Skip to content

Commit b30c8f8

Browse files
wernerlewisPatater
authored andcommitted
config: Extract memory definitions from sources
Memory regions may be defined in config sources with MBED_ROM_START and MBED_ROM_SIZE or MBED_RAM_START and MBED_RAM_SIZE. These were not extracted by the config system, and so were ignored when generating a config in mbed-tools. These attributes are extracted from config files, adding defined memory regions to the Config. Both SIZE and START are required together to add a region, and repeated definition of a memory region is ignored.
1 parent 73fc6ed commit b30c8f8

File tree

5 files changed

+134
-6
lines changed

5 files changed

+134
-6
lines changed

news/222.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for MBED_ROM_START, MBED_ROM_SIZE, MBED_RAM_START and MBED_RAM_SIZE in config system.

src/mbed_tools/build/_internal/config/config.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from collections import UserDict
99
from typing import Any, Iterable, Hashable, Callable, List
1010

11-
from mbed_tools.build._internal.config.source import Override, ConfigSetting
11+
from mbed_tools.build._internal.config.source import Memory, Override, ConfigSetting
1212

1313
logger = logging.getLogger(__name__)
1414

@@ -18,13 +18,15 @@ class Config(UserDict):
1818
1919
This object understands how to populate the different 'config sections' which all have different rules for how the
2020
settings are collected.
21-
Applies overrides, appends macros and updates config settings.
21+
Applies overrides, appends macros, updates memories, and updates config settings.
2222
"""
2323

2424
def __setitem__(self, key: Hashable, item: Any) -> None:
2525
"""Set an item based on its key."""
2626
if key == CONFIG_SECTION:
2727
self._update_config_section(item)
28+
elif key == MEMORIES_SECTION:
29+
self._update_memories_section(item)
2830
elif key == OVERRIDES_SECTION:
2931
self._handle_overrides(item)
3032
elif key == MACROS_SECTION:
@@ -67,6 +69,20 @@ def _update_config_section(self, config_settings: List[ConfigSetting]) -> None:
6769

6870
self.data[CONFIG_SECTION] = self.data.get(CONFIG_SECTION, []) + config_settings
6971

72+
def _update_memories_section(self, memories: List[Memory]) -> None:
73+
defined_memories = self.data.get(MEMORIES_SECTION, [])
74+
for memory in memories:
75+
logger.debug(f"Adding memory settings `{memory.name}: start={memory.start} size={memory.size}`")
76+
prev_defined = next((mem for mem in defined_memories if mem.name == memory.name), None)
77+
if prev_defined is None:
78+
defined_memories.append(memory)
79+
else:
80+
logger.warning(
81+
f"You are attempting to redefine `{memory.name}` from {prev_defined.namespace}.\n"
82+
f"The values from `{memory.namespace}` will be ignored"
83+
)
84+
self.data[MEMORIES_SECTION] = defined_memories
85+
7086
def _find_first_config_setting(self, predicate: Callable) -> Any:
7187
"""Find first config setting based on `predicate`.
7288
@@ -89,6 +105,7 @@ def _find_first_config_setting(self, predicate: Callable) -> Any:
89105

90106
CONFIG_SECTION = "config"
91107
MACROS_SECTION = "macros"
108+
MEMORIES_SECTION = "memories"
92109
OVERRIDES_SECTION = "overrides"
93110

94111

src/mbed_tools/build/_internal/config/source.py

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ def prepare(
2828
) -> dict:
2929
"""Prepare a config source for entry into the Config object.
3030
31-
Extracts config and override settings from the source. Flattens these nested dictionaries out into lists of
32-
objects which are namespaced in the way the Mbed config system expects.
31+
Extracts memory, config and override settings from the source. Flattens these nested dictionaries out into
32+
lists of objects which are namespaced in the way the Mbed config system expects.
3333
3434
Args:
3535
input_data: The raw config JSON object parsed from the config file.
@@ -46,6 +46,11 @@ def prepare(
4646
for key in data:
4747
data[key] = _sanitise_value(data[key])
4848

49+
memories = _extract_memories(namespace, data)
50+
51+
if memories:
52+
data["memories"] = memories
53+
4954
if "config" in data:
5055
data["config"] = _extract_config_settings(namespace, data["config"])
5156

@@ -78,6 +83,31 @@ def __post_init__(self) -> None:
7883
self.value = _sanitise_value(self.value)
7984

8085

86+
@dataclass
87+
class Memory:
88+
"""Representation of a defined RAM/ROM region."""
89+
90+
name: str
91+
namespace: str
92+
start: str
93+
size: str
94+
95+
def __post_init__(self) -> None:
96+
"""Convert start and size to hex format strings."""
97+
try:
98+
self.start = hex(int(self.start, 0))
99+
except ValueError:
100+
raise ValueError(
101+
f"Value of MBED_{self.name}_START in {self.namespace}, {self.start} is invalid: must be an integer"
102+
)
103+
try:
104+
self.size = hex(int(self.size, 0))
105+
except ValueError:
106+
raise ValueError(
107+
f"Value of MBED_{self.name}_SIZE in {self.namespace}, {self.size} is invalid: must be an integer"
108+
)
109+
110+
81111
@dataclass
82112
class Override:
83113
"""Representation of a config override.
@@ -128,6 +158,27 @@ def _extract_config_settings(namespace: str, config_data: dict) -> List[ConfigSe
128158
return settings
129159

130160

161+
def _extract_memories(namespace: str, data: dict) -> List[Memory]:
162+
memories = []
163+
for mem in ["rom", "ram"]:
164+
start_attr = f"mbed_{mem}_start"
165+
size_attr = f"mbed_{mem}_size"
166+
start = data.get(start_attr)
167+
size = data.get(size_attr)
168+
169+
if size is not None and start is not None:
170+
logger.debug(f"Extracting MBED_{mem.upper()} definitions in {namespace}: _START={start}, _SIZE={size}.")
171+
172+
memory = Memory(mem.upper(), namespace, start, size)
173+
memories.append(memory)
174+
elif start is not None or size is not None:
175+
raise ValueError(
176+
f"{size_attr.upper()} and {start_attr.upper()} must be defined together. Only "
177+
f"{'START' if start is not None else 'SIZE'} is defined in the lib {namespace}."
178+
)
179+
return memories
180+
181+
131182
def _extract_target_overrides(
132183
namespace: str, override_data: dict, allowed_target_labels: Iterable[str]
133184
) -> List[Override]:

tests/build/_internal/config/test_config.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
# Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
33
# SPDX-License-Identifier: Apache-2.0
44
#
5+
import logging
56
import pytest
67

78
from mbed_tools.build._internal.config.config import Config
8-
from mbed_tools.build._internal.config.source import prepare, ConfigSetting, Override
9+
from mbed_tools.build._internal.config.source import prepare, ConfigSetting, Memory, Override
910

1011

1112
class TestConfig:
@@ -24,6 +25,17 @@ def test_raises_when_trying_to_add_duplicate_config_setting(self):
2425
with pytest.raises(ValueError, match="lib.param already defined"):
2526
conf.update(prepare({"config": {"param": {"value": 0}}}, source_name="lib"))
2627

28+
def test_logs_ignore_mbed_ram_repeated(self, caplog):
29+
caplog.set_level(logging.DEBUG)
30+
input_dict = {"mbed_ram_size": "0x80000", "mbed_ram_start": "0x24000000"}
31+
input_dict2 = {"mbed_ram_size": "0x78000", "mbed_ram_start": "0x24200000"}
32+
33+
conf = Config(prepare(input_dict, source_name="lib1"))
34+
conf.update(prepare(input_dict2, source_name="lib2"))
35+
36+
assert "values from `lib2` will be ignored" in caplog.text
37+
assert conf["memories"] == [Memory("RAM", "lib1", "0x24000000", "0x80000")]
38+
2739
def test_target_overrides_handled(self):
2840
conf = Config(
2941
{

tests/build/_internal/config/test_source.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
# Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
33
# SPDX-License-Identifier: Apache-2.0
44
#
5+
import pytest
6+
57
from mbed_tools.build._internal.config import source
6-
from mbed_tools.build._internal.config.source import Override
8+
from mbed_tools.build._internal.config.source import Memory, Override
79

810

911
class TestPrepareSource:
@@ -118,3 +120,48 @@ def test_converts_config_setting_value_lists_to_sets(self):
118120
assert conf["config"][0].value == {"ETHERNET", "WIFI"}
119121
assert conf["sectors"] == {0, 2048}
120122
assert conf["header_info"] == {0, 2048, "bobbins", "magic"}
123+
124+
def test_memory_attr_extracted(self):
125+
lib = {
126+
"mbed_ram_size": "0x80000",
127+
"mbed_ram_start": "0x24000000",
128+
"mbed_rom_size": "0x200000",
129+
"mbed_rom_start": "0x08000000",
130+
}
131+
132+
conf = source.prepare(lib, "lib")
133+
134+
assert Memory("RAM", "lib", "0x24000000", "0x80000") in conf["memories"]
135+
assert Memory("ROM", "lib", "0x8000000", "0x200000") in conf["memories"]
136+
137+
def test_memory_attr_converted_as_hex(self):
138+
input_dict = {"mbed_ram_size": "1024", "mbed_ram_start": "0x24000000"}
139+
140+
conf = source.prepare(input_dict, source_name="lib")
141+
142+
memory, *_ = conf["memories"]
143+
assert memory.size == "0x400"
144+
145+
def test_raises_memory_size_not_integer(self):
146+
input_dict = {"mbed_ram_size": "NOT INT", "mbed_ram_start": "0x24000000"}
147+
148+
with pytest.raises(ValueError, match="_SIZE in lib, NOT INT is invalid: must be an integer"):
149+
source.prepare(input_dict, "lib")
150+
151+
def test_raises_memory_start_not_integer(self):
152+
input_dict = {"mbed_ram_size": "0x80000", "mbed_ram_start": "NOT INT"}
153+
154+
with pytest.raises(ValueError, match="_START in lib, NOT INT is invalid: must be an integer"):
155+
source.prepare(input_dict, "lib")
156+
157+
def test_raises_memory_size_defined_not_start(self):
158+
input_dict = {"mbed_ram_size": "0x80000"}
159+
160+
with pytest.raises(ValueError, match="Only SIZE is defined"):
161+
source.prepare(input_dict)
162+
163+
def test_raises_memory_start_defined_not_size(self):
164+
input_dict = {"mbed_ram_start": "0x24000000"}
165+
166+
with pytest.raises(ValueError, match="Only START is defined"):
167+
source.prepare(input_dict)

0 commit comments

Comments
 (0)