Skip to content
Snippets Groups Projects
Commit 23bbd2ee authored by Nicola Sparnacci's avatar Nicola Sparnacci
Browse files

[CORE] Add variable merge in `local.conf`


Added a function that unifies multiple assignments of a single variable
into a single assignment. For example:

```
VAR = "test"
VAR += "second-test
```
ends up with:
```
VAR += " \
   test \
   second-test \
"
```

Signed-off-by: default avatarNicola Sparnacci <nicola.sparnacci@seco.com>
parent 819b8f0a
No related branches found
No related tags found
1 merge request!39[CORE] Add variable merging & FEATURE-APPEND cluster
...@@ -383,6 +383,81 @@ def local_conf_normalize_structure(build_dir): ...@@ -383,6 +383,81 @@ def local_conf_normalize_structure(build_dir):
with open(local_conf_path, 'w') as file: with open(local_conf_path, 'w') as file:
file.writelines(reordered_config) file.writelines(reordered_config)
##
# @brief Merges duplicate Yocto variable assignments in the local.conf file.
#
# This function reads the `local.conf` file, searches for all Yocto variables,
# and merges all the values of the same variable into one line. It treats
# `:append` as equivalent to `+=` and preserves the original order of variables
# in the file. Lines that do not match the Yocto variable pattern are preserved
# as is.
#
# @param build_dir Path to the build directory containing `conf/local.conf`.
# @return None
##
def local_conf_merge_variables(build_dir):
local_conf_path = os.path.join(build_dir, "conf/local.conf")
with open(local_conf_path, "r") as file:
lines = file.readlines()
# Regex to match Yocto variable assignments, including `:append`
yocto_var_pattern = re.compile(r'^(\w+)(?::append)?\s*(\+?=)\s*"(.*)"')
# Process lines and collect variable assignments
output_lines = []
variables = {}
operators = {}
for line in lines:
match = yocto_var_pattern.match(line.strip())
if match:
var_name, operator, value = match.groups()
value = value.strip()
if var_name not in variables:
variables[var_name] = []
operators[var_name] = []
output_lines.append(line)
variables[var_name].append(value.replace("\"", ""))
# If `+=` or `:append` is used in ate least one assignment,
# ensure the final operator is `+=`
if ":append" in line:
operators[var_name].append("+=")
else:
operators[var_name].append(operator)
else:
# Preserve lines that do not match the regex
output_lines.append(line)
# Replace duplicate variables in the output lines
for lineidx, line in enumerate(output_lines):
match = yocto_var_pattern.match(line.strip())
if match:
var_name, _, _ = match.groups()
if var_name in variables:
# If the variable has multiple values, merge them into one line (removing empty
# assignments)
if len(variables[var_name]) > 1:
if all(op == "=" for op in operators[var_name]):
operator = "="
ssu.eprint(f"Warning: Found multiple '=' assignments for '{var_name}'. Merged but have a look at it.")
else:
operator = "+="
merged_values = " \\\n".join(
" " + value for value in variables[var_name] if value.strip() != ""
)
merged_line = var_name + " " + operator + ' " \\\n' + merged_values + ' \\\n"\n'
output_lines[lineidx] = merged_line
else:
output_lines[lineidx] = line
# Remove the variable after processing to avoid duplicates
del variables[var_name]
# Write the updated lines back to the file
with open(local_conf_path, "w") as file:
file.writelines(output_lines)
## ##
# @brief Extracts the Seco's layers from a given bblayers.conf. # @brief Extracts the Seco's layers from a given bblayers.conf.
# #
......
...@@ -422,6 +422,10 @@ def environment_manager(args_cwd, args_build_dir, args_sdkmachine, reconfigure, ...@@ -422,6 +422,10 @@ def environment_manager(args_cwd, args_build_dir, args_sdkmachine, reconfigure,
# local.conf normalize structure (reorder sections) # local.conf normalize structure (reorder sections)
ssc.local_conf_normalize_structure(build_dir) ssc.local_conf_normalize_structure(build_dir)
# Merge multiple variable assignment into a single assignment
ssc.local_conf_merge_variables(build_dir)
else: else:
new_build_created = False new_build_created = False
......
...@@ -842,7 +842,123 @@ class TestBuildConfClass: ...@@ -842,7 +842,123 @@ class TestBuildConfClass:
# Assert the result matches the expected output # Assert the result matches the expected output
assert result == expected_output assert result == expected_output
##
##
# @brief Test merging of variable definitions in local.conf file.
#
# @param input_content The initial content of the `local.conf` file before processing.
# @param expected_content The expected content of the `local.conf` file after processing.
# @param expect_warning Boolean indicating whether a warning is expected to be emitted.
# @param tmp_path pytest fixture providing a temporary path for test file generation.
# This test suite validates the behavior of the `local_conf_merge_variables`
# function under various scenarios involving duplicate variable declarations
# in a Yocto local.conf file. The test cases cover:
# - No duplicates (no changes expected).
# - Duplicates with assignment (`=`), which should be merged into a multiline assignment.
# - Duplicates with `+=` (append), which should be consolidated properly.
##
@pytest.mark.parametrize(
"input_content, expected_content, expect_warning",
[
# Test case 1: No duplicates, no changes
(
"""
include SRCREV.conf
TEST_VAR = "value1"
SECOND_TEST_VAR = "value1"
""",
"""
include SRCREV.conf
TEST_VAR = "value1"
SECOND_TEST_VAR = "value1"
""",
False,
),
# Test case 2: Duplicate variables with `=`
(
"""
include SRCREV.conf
TEST_VAR = "value1"
TEST_VAR = "value2"
""",
"""
include SRCREV.conf
TEST_VAR = " \\
value1 \\
value2 \\
"
""",
True,
),
# Test case 3: Duplicate variables with `:append`
(
"""
include SRCREV.conf
TEST_VAR = "value1"
TEST_VAR += "value2"
""",
"""
include SRCREV.conf
TEST_VAR += " \\
value1 \\
value2 \\
"
""",
False,
),
# Test case 4: Duplicate variables with `:append`
(
"""
include SRCREV.conf
TEST_VAR = "value1"
TEST_VAR += " "
TEST_VAR += "value2"
""",
"""
include SRCREV.conf
TEST_VAR += " \\
value1 \\
value2 \\
"
""",
False,
),
],
)
def test_local_conf_merge_variables(self, input_content, expected_content, expect_warning, tmp_path):
# Create the temporary directory structure
build_dir = tmp_path / "build"
conf_dir = build_dir / "conf"
conf_dir.mkdir(parents=True)
conf_file = conf_dir / "local.conf"
# Create the local.conf file with sample content
conf_file.write_text(input_content)
# Call the function to test
with patch('seco_setup_utils.eprint') as mock_eprint:
ssc.local_conf_merge_variables(build_dir)
if (expect_warning):
mock_eprint.assert_called_once()
# Normalize both actual and expected content
result_content = conf_file.read_text()
normalized_result = result_content.strip()
normalized_expected = expected_content.strip()
# Assert that the normalized content matches
assert normalized_result == normalized_expected
# @brief # @brief
# Test the `local_conf_normalize_structure` function. # Test the `local_conf_normalize_structure` function.
# #
......
...@@ -310,13 +310,15 @@ class TestEnvClass: ...@@ -310,13 +310,15 @@ class TestEnvClass:
patch("seco_setup_conf.generate_srcrev_conf") as mock_generate_srcrev_conf, \ patch("seco_setup_conf.generate_srcrev_conf") as mock_generate_srcrev_conf, \
patch("seco_setup_conf.local_conf_sanity_check") as mock_local_conf_sanity_check, \ patch("seco_setup_conf.local_conf_sanity_check") as mock_local_conf_sanity_check, \
patch("seco_setup_conf.local_conf_normalize_structure") as mock_local_conf_normalize_structure, \ patch("seco_setup_conf.local_conf_normalize_structure") as mock_local_conf_normalize_structure, \
patch("seco_setup_conf.local_conf_merge_variables") as mock_local_conf_merge_variables, \
patch("seco_setup_conf.print_welcome_message") as mock_print_welcome_message: patch("seco_setup_conf.print_welcome_message") as mock_print_welcome_message:
yield (mock_get_cwd, mock_os_getcwd, mock_exists_build_dir, mock_set_sdkmachine, yield (mock_get_cwd, mock_os_getcwd, mock_exists_build_dir, mock_set_sdkmachine,
mock_setup_environment, mock_eprint, mock_run_get_config, mock_setup_environment, mock_eprint, mock_run_get_config,
mock_clusterize_config, mock_prepare_local_conf, mock_clusterize_config, mock_prepare_local_conf,
mock_prepare_bblayers_conf, mock_customize_build, mock_prepare_bblayers_conf, mock_customize_build,
mock_generate_srcrev_conf, mock_local_conf_sanity_check, mock_generate_srcrev_conf, mock_local_conf_sanity_check,
mock_local_conf_normalize_structure, mock_print_welcome_message) mock_local_conf_normalize_structure, mock_local_conf_merge_variables,
mock_print_welcome_message)
## @brief Test environment_manager with various scenarios. ## @brief Test environment_manager with various scenarios.
# #
...@@ -346,7 +348,8 @@ class TestEnvClass: ...@@ -346,7 +348,8 @@ class TestEnvClass:
mock_clusterize_config, mock_prepare_local_conf, mock_clusterize_config, mock_prepare_local_conf,
mock_prepare_bblayers_conf, mock_customize_build, mock_prepare_bblayers_conf, mock_customize_build,
mock_generate_srcrev_conf, mock_local_conf_sanity_check, mock_generate_srcrev_conf, mock_local_conf_sanity_check,
mock_local_conf_normalize_structure, mock_print_welcome_message) = mock_environment_manager mock_local_conf_normalize_structure, mock_local_conf_merge_variables,
mock_print_welcome_message) = mock_environment_manager
# Mock behavior for tests # Mock behavior for tests
pwd = "/some/current/dir" pwd = "/some/current/dir"
...@@ -384,6 +387,7 @@ class TestEnvClass: ...@@ -384,6 +387,7 @@ class TestEnvClass:
mock_generate_srcrev_conf.assert_called_once() mock_generate_srcrev_conf.assert_called_once()
mock_local_conf_sanity_check.assert_called_once() mock_local_conf_sanity_check.assert_called_once()
mock_local_conf_normalize_structure.assert_called_once() mock_local_conf_normalize_structure.assert_called_once()
mock_local_conf_merge_variables.assert_called_once()
if args_build_dir != None and reconfigure == False: if args_build_dir != None and reconfigure == False:
mock_print_welcome_message.assert_any_call(customize_build_result, None, None, None, None) mock_print_welcome_message.assert_any_call(customize_build_result, None, None, None, None)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment