This commit is contained in:
iranl
2025-06-10 20:30:35 +02:00
parent db74f2797c
commit 56ecb87109
238 changed files with 89398 additions and 57 deletions

View File

@@ -0,0 +1,38 @@
# Parsing and generating code
This folder contains scripts for parsing `esp_wifi` component headers and configs and generates configs, headers and sources of `esp_wifi_remote`.
The CI job runs the parse and generation step, after which the generated files appear locally in this folder. Then the CI jobs compares the generated files with the specific version files. In case of a mismatch (CI failure), please check if the generated file is correct. If so, update the component and create a new version. If not, update the scripts.
## Parsing `esp_wifi` headers
Extract prototypes from the selected wifi headers
## Generating `Kconfig`
* Kconfig -- we only replace `SOC_WIFI_` -> `SLAVE_SOC_WIFI_` and `IDF_TARGET_` -> `SLAVE_IDF_TARGET_` configs
* Kconfig.soc_wifi_caps.in -- we parse `IDF/components/soc/$SLAVE/include/soc/Kconfig.soc_caps.in` for all slaves and collect `SOC_WIFI_` configs, which we enclose with `if SLAVE_IDF_TARGET...` ... `endif`.
This way the slave's WiFi capabilities become runtime available in `esp_wifi_remote` submenu
## Generating headers and forwarding to `esp_hosted`
Based on extracted WiFi function prototypes we generate
* `esp_wifi_remote_api.h` -- declares all wifi APIs with `esp_wifi_remote...()` prefix
* `esp_wifi_with_remote.c` -- defines all original wifi APIs that forward calls for `esp_wifi_remote` -- This file is used only for targets with no WiFi capabilities, so the original WiFi APIs could be called the same way as on WiFi targets. (this file is not compiled for targets with WiFi caps)
* `esp_wifi_remote_with_hosted.h` -- defines `static inline` functions that directly forward calls from `esp_wifi_remote` to `esp_hosted`
## Generating slave's WiFi native types
* `esp_wifi_types_native.h` -- defines specific WiFi types based on the selected WiFI slave and its capabilities. We use the original header and only replace `CONFIG_SOC_WIFI_` -> `CONFIG_SLAVE_SOC_WIFI_` and `CONFIG_IDF_TARGET_` -> `CONFIG_SLAVE_IDF_TARGET_` configs
## Generating test cases
This component includes a simple build-only test that exercises all APIs from `esp_wifi_remote` (and also from `esp_wifi`). The test is also generated by the same script, since we know the function prototypes, so we could tell what parameters we call the exercised APIs.
This test includes a mocked version of the `esp_hosted` component, so the generated files are:
* `esp_hosted_mock.c` -- all WiFi APIs to just return a default value (typically `ESP_OK`)
* `esp_hosted_mock.h` -- declares all WiFi related APIs
* `Kconfig` -- selection of all SLAVE targets (with WiFi capabilities)
* `all_wifi_calls.c` -- calls all WiFi APIs (to check that targets without WiFi caps can use the original APIs)
* `all_wifi_remote_calls.c` -- calls all remote WiFi APIs (to check that also the targets with WiFi caps can use the remote wifi functionality)

View File

@@ -0,0 +1,6 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// This file is auto-generated

View File

@@ -0,0 +1,636 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import argparse
import glob
import json
import os
import re
import shutil
import subprocess
from collections import namedtuple
from idf_build_apps.constants import PREVIEW_TARGETS, SUPPORTED_TARGETS
from pycparser import c_ast, c_parser, preprocess_file
Param = namedtuple('Param', ['ptr', 'array', 'qual', 'type', 'name'])
AUTO_GENERATED = 'This file is auto-generated'
COPYRIGHT_HEADER = open('copyright_header.h', 'r').read()
NAMESPACE = re.compile(r'^esp_wifi')
DEPRECATED_API = ['esp_wifi_set_ant_gpio', 'esp_wifi_get_ant', 'esp_wifi_get_ant_gpio', 'esp_wifi_set_ant']
KCONFIG_MULTIPLE_DEF = '# ignore: multiple-definition'
wifi_configs = []
class FunctionVisitor(c_ast.NodeVisitor):
def __init__(self, header):
self.function_prototypes = {}
self.ptr = 0
self.array = 0
self.content = open(header, 'r').read()
def get_type(self, node, suffix='param'):
if suffix == 'param':
self.ptr = 0
self.array = 0
if isinstance(node.type, c_ast.TypeDecl):
typename = node.type.declname
quals = ''
if node.type.quals:
quals = ' '.join(node.type.quals)
if node.type.type.names:
type = node.type.type.names[0]
return quals, type, typename
if isinstance(node.type, c_ast.PtrDecl):
quals, type, name = self.get_type(node.type, 'ptr')
self.ptr += 1
return quals, type, name
if isinstance(node.type, c_ast.ArrayDecl):
quals, type, name = self.get_type(node.type, 'array')
self.array = int(node.type.dim.value)
return quals, type, name
def visit_FuncDecl(self, node):
if isinstance(node.type, c_ast.TypeDecl):
func_name = node.type.declname
if func_name.startswith('esp_wifi') and not func_name.endswith('_t') and func_name in self.content:
if func_name in DEPRECATED_API:
return
ret = node.type.type.names[0]
args = []
for param in node.args.params:
quals, type, name = self.get_type(param)
param = Param(ptr=self.ptr, array=self.array, qual=quals, type=type, name=name)
args.append(param)
self.function_prototypes[func_name] = (ret, args)
# Parse the header file and extract function prototypes
def extract_function_prototypes(header_code, header):
parser = c_parser.CParser() # Set debug parameter to False
ast = parser.parse(header_code)
visitor = FunctionVisitor(header)
visitor.visit(ast)
return visitor.function_prototypes
def exec_cmd(what, out_file=None):
p = subprocess.Popen(what, stdin=subprocess.PIPE, stdout=out_file if out_file is not None else subprocess.PIPE, stderr=subprocess.PIPE)
output_b, err_b = p.communicate()
rc = p.returncode
output: str = output_b.decode('utf-8') if output_b is not None else ''
err: str = err_b.decode('utf-8') if err_b is not None else ''
return rc, output, err, ' '.join(what)
def preprocess(idf_path, header):
project_dir = os.path.join(idf_path, 'examples', 'wifi', 'getting_started', 'station')
build_dir = os.path.join(project_dir, 'build')
subprocess.check_call(['idf.py', '-B', build_dir, 'reconfigure'], cwd=project_dir)
build_commands_json = os.path.join(build_dir, 'compile_commands.json')
with open(build_commands_json, 'r', encoding='utf-8') as f:
build_command = json.load(f)[0]['command'].split()
include_dir_flags = []
include_dirs = []
# process compilation flags (includes and defines)
for item in build_command:
if item.startswith('-I'):
include_dir_flags.append(item)
if 'components' in item:
include_dirs.append(item[2:]) # Removing the leading "-I"
if item.startswith('-D'):
include_dir_flags.append(item.replace('\\','')) # removes escaped quotes, eg: -DMBEDTLS_CONFIG_FILE=\\\"mbedtls/esp_config.h\\\"
include_dir_flags.append('-I' + os.path.join(build_dir, 'config'))
temp_file = 'esp_wifi_preprocessed.h'
with open(temp_file, 'w') as f:
f.write('#define asm\n')
f.write('#define volatile\n')
f.write('#define __asm__\n')
f.write('#define __volatile__\n')
with open(temp_file, 'a') as f:
rc, out, err, cmd = exec_cmd(['xtensa-esp32-elf-gcc', '-w', '-P', '-include', 'ignore_extensions.h', '-E', header] + include_dir_flags, f)
if rc != 0:
print(f'command {cmd} failed!')
print(err)
preprocessed_code = preprocess_file(temp_file)
return preprocessed_code
def get_args(parameters):
params = []
names = []
for param in parameters:
typename = param.type
if typename == 'void' and param.ptr == 0 and param.name is None:
params.append(f'{typename}')
continue
if param.qual != '':
typename = f'{param.qual} ' + typename
declname = param.name
names.append(f'{declname}')
if param.ptr > 0:
declname = '*' * param.ptr + declname
if param.array > 0:
declname += f'[{param.array}]'
params.append(f'{typename} {declname}')
comma_separated_params = ', '.join(params)
comma_separated_names = ', '.join(names)
return comma_separated_params, comma_separated_names
def get_vars(parameters):
definitions = ''
names = []
for param in parameters:
typename = param.type
if typename == 'void' and param.ptr == 0 and param.name is None:
continue
default_value = '0'
declname = param.name
names.append(f'{declname}')
if param.qual != '':
typename = f'{param.qual} ' + typename
if param.ptr > 0:
declname = '*' * param.ptr + declname
default_value = 'NULL'
if param.array > 0:
declname += f'[{param.array}]'
default_value = '{}'
definitions += f' {typename} {declname} = {default_value};\n'
comma_separated_names = ', '.join(names)
return definitions, comma_separated_names
def generate_kconfig_wifi_caps(idf_path, idf_ver_dir, component_path):
kconfig = os.path.join(component_path, idf_ver_dir, 'Kconfig.soc_wifi_caps.in')
slave_select = os.path.join(component_path, idf_ver_dir, 'Kconfig.slave_select.in')
# Read and parse the global Kconfig file for target selections
target_selections = {}
current_target = None
with open(os.path.join(idf_path, 'Kconfig'), 'r') as f:
for line in f:
line = line.strip()
if line.startswith('config IDF_TARGET_'):
current_target = line.split()[1]
target_selections[current_target] = []
elif current_target and line.strip().startswith('select'):
selection = line.split()[1]
target_selections[current_target].append(selection)
with open(kconfig, 'w') as slave_caps, open(slave_select, 'w') as slave:
slave_caps.write(f'# {AUTO_GENERATED}\n')
slave.write(f'# {AUTO_GENERATED}\n')
slave.write(' choice SLAVE_IDF_TARGET\n')
slave.write(' prompt "choose slave target"\n')
slave.write(' default SLAVE_IDF_TARGET_ESP32\n')
for slave_target in SUPPORTED_TARGETS + PREVIEW_TARGETS:
add_slave = False
kconfig_content = []
soc_caps = os.path.join(idf_path, 'components', 'soc', slave_target, 'include', 'soc', 'Kconfig.soc_caps.in')
try:
with open(soc_caps, 'r') as f:
for line in f:
if line.strip().startswith('config SOC_WIFI_'):
if 'config SOC_WIFI_SUPPORTED' in line:
# if WiFi supported for this target, add it to Kconfig slave options and test this slave
add_slave = True
replaced = re.sub(r'SOC_WIFI_', 'SLAVE_SOC_WIFI_', line.strip())
kconfig_content.append(f' {replaced} {KCONFIG_MULTIPLE_DEF}\n')
kconfig_content.append(f' {f.readline()}') # type
kconfig_content.append(f' {f.readline()}\n') # default
except FileNotFoundError:
print(f'Warning: File {soc_caps} not found. Skipping target {slave_target}.')
continue
except IOError as e:
print(f'Error reading file {soc_caps}: {e}')
continue
if add_slave:
slave_caps.write(f'\nif SLAVE_IDF_TARGET_{slave_target.upper()}\n\n')
slave_caps.writelines(kconfig_content)
# Add any target-specific selections from global Kconfig
target_key = f'IDF_TARGET_{slave_target.upper()}'
if target_key in target_selections:
for selection in target_selections[target_key]:
slave_caps.write(f' config SLAVE_{selection} {KCONFIG_MULTIPLE_DEF}\n')
slave_caps.write(f' bool\n')
slave_caps.write(f' default y\n\n')
slave_caps.write(f'endif # {slave_target.upper()}\n')
slave_config_name = 'SLAVE_IDF_TARGET_' + slave_target.upper()
slave.write(f' config {slave_config_name}\n')
slave.write(f' bool "{slave_target}"\n')
slave.write(' endchoice\n')
return [kconfig, slave_select]
def generate_remote_wifi_api(function_prototypes, idf_ver_dir, component_path):
header = os.path.join(component_path, idf_ver_dir, 'include', 'esp_wifi_remote_api.h')
wifi_source = os.path.join(component_path, idf_ver_dir, 'esp_wifi_with_remote.c')
remote_source = os.path.join(component_path, idf_ver_dir, 'esp_wifi_remote_weak.c')
with open(header, 'w') as f:
f.write(COPYRIGHT_HEADER)
f.write('#pragma once\n')
for func_name, args in function_prototypes.items():
params, _ = get_args(args[1])
remote_func_name = NAMESPACE.sub('esp_wifi_remote', func_name)
f.write(f'{args[0]} {remote_func_name}({params});\n')
with open(wifi_source, 'w') as wifi, open(remote_source, 'w') as remote:
wifi.write(COPYRIGHT_HEADER)
wifi.write('#include "esp_wifi.h"\n')
wifi.write('#include "esp_wifi_remote.h"\n')
remote.write(COPYRIGHT_HEADER)
remote.write('#include "esp_wifi_remote.h"\n')
remote.write('#include "esp_log.h"\n\n')
remote.write('#define WEAK __attribute__((weak))\n')
remote.write('#define LOG_UNSUPPORTED_AND_RETURN(ret) ESP_LOGW("esp_wifi_remote_weak", "%s unsupported", __func__); \\\n return ret;\n')
for func_name, args in function_prototypes.items():
remote_func_name = NAMESPACE.sub('esp_wifi_remote', func_name)
params, names = get_args(args[1])
ret_type = args[0]
ret_value = '-1' # default return value indicating error
if (ret_type == 'esp_err_t'):
ret_value = 'ESP_ERR_NOT_SUPPORTED'
wifi.write(f'\n{args[0]} {func_name}({params})\n')
wifi.write('{\n')
wifi.write(f' return {remote_func_name}({names});\n')
wifi.write('}\n')
remote.write(f'\nWEAK {args[0]} {remote_func_name}({params})\n')
remote.write('{\n')
remote.write(f' LOG_UNSUPPORTED_AND_RETURN({ret_value});\n')
remote.write('}\n')
return [header, wifi_source, remote_source]
def generate_hosted_mocks(function_prototypes, idf_ver_dir, component_path):
if 'tag' in idf_ver_dir:
return []
source = os.path.join(component_path, 'test', 'smoke_test', 'components', 'esp_hosted', idf_ver_dir, 'esp_hosted_mock.c')
header = os.path.join(component_path, 'test', 'smoke_test', 'components', 'esp_hosted', idf_ver_dir, 'include', 'esp_hosted_mock.h')
with open(source, 'w') as f, open(header, 'w') as h:
f.write(COPYRIGHT_HEADER)
h.write(COPYRIGHT_HEADER)
h.write('#pragma once\n')
f.write('#include "esp_wifi.h"\n')
f.write('#include "esp_wifi_remote.h"\n')
for func_name, args in function_prototypes.items():
hosted_func_name = NAMESPACE.sub('esp_wifi_remote', func_name)
params, names = get_args(args[1])
ret_type = args[0]
ret_value = '0' # default return value
if (ret_type == 'esp_err_t'):
ret_value = 'ESP_OK'
f.write(f'\n{ret_type} {hosted_func_name}({params})\n')
f.write('{\n')
f.write(f' return {ret_value};\n')
f.write('}\n')
h.write(f'{ret_type} {hosted_func_name}({params});\n')
return [source, header]
def generate_test_cases(function_prototypes, idf_ver_dir, component_path):
if 'tag' in idf_ver_dir:
return []
wifi_cases = os.path.join(component_path, 'test', 'smoke_test', 'main', idf_ver_dir, 'all_wifi_calls.c')
remote_wifi_cases = os.path.join(component_path, 'test', 'smoke_test', 'main', idf_ver_dir, 'all_wifi_remote_calls.c')
with open(wifi_cases, 'w') as wifi, open(remote_wifi_cases, 'w') as remote:
wifi.write(COPYRIGHT_HEADER)
remote.write(COPYRIGHT_HEADER)
wifi.write('#include "esp_wifi.h"\n\n')
remote.write('#include "esp_wifi_remote.h"\n\n')
wifi.write('void run_all_wifi_apis(void)\n{\n')
remote.write('void run_all_wifi_remote_apis(void)\n{\n')
for func_name, args in function_prototypes.items():
remote_func_name = NAMESPACE.sub('esp_wifi_remote', func_name)
defs, names = get_vars(args[1])
wifi.write(' {\n')
wifi.write(f'{defs}')
wifi.write(f' {func_name}({names});\n')
wifi.write(' }\n\n')
remote.write(' {\n')
remote.write(f'{defs}')
remote.write(f' {remote_func_name}({names});\n')
remote.write(' }\n\n')
wifi.write('}\n')
remote.write('}\n')
return [wifi_cases, remote_wifi_cases]
def generate_wifi_native(idf_path, idf_ver_dir, component_path):
native_headers = []
def replace_configs(original, replaced):
with open(original, 'r') as f:
content = f.read()
content = content.replace(r'CONFIG_ESP_WIFI_', 'CONFIG_WIFI_RMT_')
content = content.replace(r'CONFIG_SOC_WIFI_', 'CONFIG_SLAVE_SOC_WIFI_')
content = content.replace(r'CONFIG_FREERTOS_', 'CONFIG_SLAVE_FREERTOS_')
content = content.replace(r'CONFIG_IDF_TARGET_', 'CONFIG_SLAVE_IDF_TARGET_')
with open(replaced, 'w') as f:
f.write(content)
return replaced
include_dir = os.path.join(idf_path, 'components', 'esp_wifi', 'include')
remote_dir = os.path.join(component_path, idf_ver_dir, 'include', 'injected')
os.makedirs(remote_dir, exist_ok=True)
header_mappings = [
('local/esp_wifi_types_native.h', 'esp_wifi_types_native.h'),
('esp_wifi.h', 'esp_wifi.h'),
('esp_wifi_types_generic.h', 'esp_wifi_types_generic.h'),
('esp_wifi_he_types.h', 'esp_wifi_he_types.h'),
('esp_wifi_types.h', 'esp_wifi_types.h'),
]
for src_rel, dest_name in header_mappings:
original = os.path.join(include_dir, src_rel)
replaced = os.path.join(remote_dir, dest_name)
native_headers.append(replace_configs(original, replaced))
# Copy remaining headers
for pattern in ['esp_wifi*.h', 'esp_mesh*.h', 'esp_now.h']:
for header in glob.glob(os.path.join(include_dir, pattern)):
dest = os.path.join(remote_dir, os.path.basename(header))
if dest not in native_headers:
shutil.copy(header, dest)
native_headers.append(dest)
return native_headers
def get_global_configs(idf_path):
"""Extract global configs from Kconfig that are selected by IDF_TARGET configs"""
global_configs = set()
current_target = None
with open(os.path.join(idf_path, 'Kconfig'), 'r') as f:
for line in f:
line = line.strip()
if line.startswith('config IDF_TARGET_'):
current_target = True
elif current_target and line.strip().startswith('select'):
selection = line.split()[1]
global_configs.add(selection)
elif line.startswith('config') or line.startswith('choice'):
current_target = False
return global_configs
def generate_kconfig(idf_path, idf_ver_dir, component_path):
# Define file paths
remote_kconfig = os.path.join(component_path, idf_ver_dir, 'Kconfig.wifi.in')
esp_wifi_kconfig = os.path.join(idf_path, 'components', 'esp_wifi', 'Kconfig')
# Variables for accumulating configuration details
remote_configs = ''
current_config = None
current_config_indent = 0
depends_on = None
# Config prefixes to be replaced
base_configs = ['SOC_WIFI_', 'IDF_TARGET_']
# Get global configs from the original Kconfig (assumed to be defined elsewhere)
global_configs = get_global_configs(idf_path)
# Read the source Kconfig lines
with open(esp_wifi_kconfig, 'r') as f:
lines = f.readlines()
# Setup counters and flags for nested ifs and choice blocks
nesting_threshold = 100 # Initial high threshold; will be adjusted later
nested_if = 0
initial_indent = -1 # Will be set once the first config/choice line is encountered
inside_choice = False
with open(remote_kconfig, 'w') as outfile:
# Write header comments
outfile.write('# Wi-Fi configuration\n')
outfile.write(f'# {AUTO_GENERATED}\n')
for original_line in lines:
stripped_line = original_line.strip()
# Update nesting level counters for if/endif blocks
if re.match(r'^if\s+[A-Z_0-9]+\s*$', stripped_line):
nested_if += 1
elif stripped_line.startswith('endif'):
nested_if -= 1
# Track whether we are inside a choice block
if re.match(r'^choice', stripped_line):
inside_choice = True
elif stripped_line.startswith('endchoice'):
inside_choice = False
# Process lines after reaching the nesting threshold
if nested_if >= nesting_threshold:
# Capture the initial indentation level when encountering the first config/choice
if initial_indent == -1 and re.match(r'^(config|choice)\s+ESP_WIFI_', stripped_line):
initial_indent = len(re.match(r'^\s*', original_line).group())
# Make a copy of the original line for modifications
modified_line = original_line
# Replace base configuration prefixes
for config in base_configs:
modified_line = re.compile(config).sub('SLAVE_' + config, modified_line)
# Replace global config names (ensuring whole word match)
for config in global_configs:
modified_line = re.compile(r'\b' + config + r'\b').sub('SLAVE_' + config, modified_line)
# Replace the primary prefix ESP_WIFI_ with WIFI_RMT_
modified_line = re.compile(r'\bESP_WIFI_').sub('WIFI_RMT_', modified_line)
# If a current config block is active, check for changes in indentation
if current_config is not None:
# If the current line is still within the current config block (by indent)
if len(re.match(r'^\s*', original_line).group()) > current_config_indent:
# Check for config properties: type (int or bool) or dependency
for keyword in ['int', 'bool', 'depends']:
if re.match(fr'^{keyword}', stripped_line):
if keyword == 'depends':
depends_on = modified_line.strip()
else:
config_type = keyword
# Prepare the default remote value by replacing the prefix
remote_value = current_config.replace('ESP_WIFI_', 'WIFI_RMT_')
else:
# End of the current config block; append the accumulated remote config block
if config_type == 'bool':
remote_configs += f'if {remote_value}\n'
remote_configs += f' config {current_config} {KCONFIG_MULTIPLE_DEF}\n'
remote_configs += f' {config_type}\n'
if depends_on is not None:
remote_configs += f' {depends_on}\n'
depends_on = None
remote_configs += f' default {remote_value}\n'
if config_type == 'bool':
remote_configs += f'endif\n'
remote_configs += '\n'
current_config = None
# Check if the line declares a config or choice for ESP_WIFI_
match = re.match(r'^(config|choice)\s+(ESP_WIFI_[A-Z0-9_]+)', stripped_line)
if match:
wifi_configs.append(match.group(2))
# Only treat non-choice config declarations as starting a config block
if match.group(1) == 'config' and not inside_choice:
current_config = match.group(2)
current_config_indent = len(re.match(r'^\s*', modified_line).group())
# Adjust indentation relative to the captured initial indent
if len(modified_line) > initial_indent and modified_line[:initial_indent].isspace():
modified_line = modified_line[initial_indent:]
outfile.write(modified_line)
# When an ESP_WIFI_ENABLED condition is encountered, update the nesting threshold
if re.match(r'^if\s+\(?ESP_WIFI_ENABLED', stripped_line):
nesting_threshold = nested_if
# Append the accumulated remote configurations under the disabled ESP_WIFI condition
outfile.write('if !ESP_WIFI_ENABLED\n')
outfile.write(remote_configs)
outfile.write('endif # ESP_WIFI_ENABLED\n')
outfile.write('# Wi-Fi configuration end\n')
return [remote_kconfig]
def generate_wifi_static(idf_vr_dir, component_path):
wifi_static = os.path.join(component_path, idf_ver_dir, 'include', 'esp_wifi_default_config.h')
with open(wifi_static, 'w') as f:
f.write(COPYRIGHT_HEADER)
f.write('#pragma once\n')
kconfig_path = os.path.join(component_path, 'Kconfig')
all_targets = SUPPORTED_TARGETS + PREVIEW_TARGETS
for target in all_targets:
if target == 'linux':
continue
default_config = f'default.{target}'
with open(default_config, 'w') as file:
for slave_target in all_targets:
value = 'y' if slave_target == target else 'n'
file.write(f'CONFIG_SLAVE_IDF_TARGET_{slave_target.upper()}={value}\n')
rc, _, err, cmd = exec_cmd(['python', '-m', 'kconfgen', '--kconfig', f'{kconfig_path}',
'--config', f'{default_config}', '--output', 'header', f'{default_config}'])
if rc != 0:
print(f'command {cmd} failed!')
print(err)
with open(wifi_static, 'a') as f:
f.write(f'\n#if CONFIG_SLAVE_IDF_TARGET_{target.upper()}\n')
with open(default_config, 'r') as config_file:
for line in config_file:
if line.strip().startswith('#define'):
f.write(line)
f.write(f'#endif // CONFIG_SLAVE_IDF_TARGET_{target.upper()}\n')
return [wifi_static]
def compare_files(base_dir, component_path, files_to_check):
failures = []
for file_path in files_to_check:
relative_path = os.path.relpath(file_path, component_path)
base_file = os.path.join(base_dir, relative_path)
if not os.path.exists(base_file):
failures.append((relative_path, 'File does not exist in base directory'))
continue
diff_cmd = ['diff', '-I', 'Copyright', file_path, base_file]
result = subprocess.run(diff_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode != 0: # diff returns 0 if files are identical
failures.append((relative_path, result.stdout.decode('utf-8')))
return failures
def get_idf_ver_dir(idf_path, component_path):
try:
# Run `git describe` inside the IDF_PATH directory
result = subprocess.run(
['git', 'describe', '--tags', '--dirty'],
cwd=idf_path,
capture_output=True,
text=True,
check=True
)
idf_ver = result.stdout.strip()
except subprocess.CalledProcessError:
raise RuntimeError('Failed to retrieve IDF version using `git describe`.')
# Regex to match version tags like vX.Y or vX.Y.Z (with optional -dirty)
match = re.match(r'^(v\d+\.\d+(\.\d+)?)(-dirty)?$', idf_ver)
if match and os.path.isdir(os.path.join(component_path, f'idf_tag_{match.group(1)}')):
return f'idf_tag_{match.group(1)}' # Return the clean tag (without -dirty)
idf_version = os.getenv('ESP_IDF_VERSION')
if idf_version is None:
raise RuntimeError("Environment variable 'ESP_IDF_VERSION' wasn't set.")
return f'idf_v{idf_version}'
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description='Build all projects',
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
epilog='''\
TEST FAILED
-----------
Some of the component files are different from the pregenerated output.
Please check the files that marked as "FAILED" in this step,
typically you'd just need to commit the changes and create a new version.
Please be aware that the pregenerated files use the same copyright header, so after
making changes you might need to modify 'copyright_header.h' in the script directory.
''')
parser.add_argument('-s', '--skip-check', help='Skip checking the versioned files against the re-generated', action='store_true')
parser.add_argument('--base-dir', help='Base directory to compare generated files against')
args = parser.parse_args()
idf_path = os.getenv('IDF_PATH')
if idf_path is None:
raise RuntimeError("Environment variable 'IDF_PATH' wasn't set.")
component_path = os.path.normpath(os.path.join(os.path.realpath(__file__),'..', '..'))
idf_path = os.getenv('IDF_PATH')
if idf_path is None:
raise RuntimeError("Environment variable 'IDF_PATH' wasn't set.")
idf_ver_dir = get_idf_ver_dir(idf_path, component_path)
header = os.path.join(idf_path, 'components', 'esp_wifi', 'include', 'esp_wifi.h')
function_prototypes = extract_function_prototypes(preprocess(idf_path, header), header)
files_to_check = []
files_to_check += generate_kconfig_wifi_caps(idf_path, idf_ver_dir, component_path)
files_to_check += generate_remote_wifi_api(function_prototypes, idf_ver_dir, component_path)
files_to_check += generate_hosted_mocks(function_prototypes, idf_ver_dir, component_path)
files_to_check += generate_test_cases(function_prototypes, idf_ver_dir, component_path)
files_to_check += generate_wifi_native(idf_path, idf_ver_dir, component_path)
files_to_check += generate_kconfig(idf_path, idf_ver_dir, component_path)
files_to_check += generate_wifi_static(idf_ver_dir, component_path)
for f in files_to_check:
print(f)
if args.skip_check or args.base_dir is None:
exit(0)
failures = compare_files(args.base_dir, component_path, files_to_check)
if failures:
print(parser.epilog)
print('\nDifferent files:\n')
for file, diff in failures:
print(f'{file}\nChanges:\n{diff}')
exit(1)
else:
print('All files are identical to the base directory.')
exit(0)

View File

@@ -0,0 +1,33 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import os
import re
import sys
if len(sys.argv) < 2:
print('Usage: python generate_slave_configs.py <output_directory>')
sys.exit(1)
output_directory = sys.argv[1]
# Input Kconfig file
component_path = os.path.normpath(os.path.join(os.path.realpath(__file__),'..', '..'))
kconfig_file = os.path.join(component_path, f"idf_v{os.getenv('ESP_IDF_VERSION')}", 'Kconfig.slave_select.in')
# Output file prefix
output_prefix = 'sdkconfig.ci.'
# Regex pattern to match all available options for SLAVE_IDF_TARGET
pattern = r'^ *config SLAVE_IDF_TARGET_(\w+)'
# Read the Kconfig and generate specific sdkconfig.ci.{slave} for each option
with open(kconfig_file, 'r') as file:
for line in file:
match = re.match(pattern, line)
if match:
slave = match.group(1)
output_file = os.path.join(output_directory, f'{output_prefix}{slave.lower()}')
with open(output_file, 'w') as out_file:
out_file.write(f'CONFIG_SLAVE_IDF_TARGET_{slave.upper()}=y\n')
print(f'Generated: {output_file}')

View File

@@ -0,0 +1,13 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#define __attribute__(x)
#define __asm__(x)
#define __volatile__(...)
#define volatile(...)
typedef void *__builtin_va_list;
#define __inline__ inline
#define __inline inline
#define __extension__