Skip to content

Commit 9da9ec0

Browse files
authored
targets: standardise target type name normalisation (#1432)
This change fixes issues using DFP targets with dashes or other punctuation in the name.
1 parent dde4e8d commit 9da9ec0

File tree

5 files changed

+55
-10
lines changed

5 files changed

+55
-10
lines changed

pyocd/board/board.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# pyOCD debugger
22
# Copyright (c) 2006-2013,2018 Arm Limited
3-
# Copyright (c) 2021 Chris Reed
3+
# Copyright (c) 2021-2022 Chris Reed
44
# SPDX-License-Identifier: Apache-2.0
55
#
66
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,7 +19,7 @@
1919
from typing import (Any, Optional, TYPE_CHECKING)
2020

2121
from ..core import exceptions
22-
from ..target import TARGET
22+
from ..target import (TARGET, normalise_target_type_name)
2323
from ..target.pack import pack_target
2424
from ..utility.graph import GraphNode
2525

@@ -84,7 +84,7 @@ def __init__(self,
8484
assert target is not None
8585

8686
# Convert dashes to underscores in the target type, and convert to lower case.
87-
target = target.replace('-', '_').lower()
87+
target = normalise_target_type_name(target)
8888

8989
# Write the effective target type back to options if it's different.
9090
if target != session.options.get('target_override'):

pyocd/target/__init__.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# pyOCD debugger
22
# Copyright (c) 2013-2019 Arm Limited
3+
# Copyright (c) 2022 Chris Reed
34
# SPDX-License-Identifier: Apache-2.0
45
#
56
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,10 +15,35 @@
1415
# See the License for the specific language governing permissions and
1516
# limitations under the License.
1617

18+
import string
19+
1720
from .builtin import BUILTIN_TARGETS
1821

1922
## @brief Dictionary of all targets.
2023
#
2124
# This table starts off with only the builtin targets. At runtime it may be extended with
2225
# additional targets from CMSIS DFPs or other sources.
2326
TARGET = BUILTIN_TARGETS.copy()
27+
28+
## @brief Legal characters in target type names.
29+
#
30+
# Basically, C language identifier characters.
31+
_TARGET_TYPE_NAME_CHARS = string.ascii_letters + string.digits + '_'
32+
33+
def normalise_target_type_name(target_type: str) -> str:
34+
"""@brief Normalise a target type name.
35+
36+
The output string has all non-ASCII alphanumeric characters replaced with underscores and is
37+
converted to all lowercase. Only one underscore in a row will be inserted in the output. For example,
38+
"foo--bar" will be normalised to "foo_bar".
39+
"""
40+
result = ""
41+
in_replace = False
42+
for c in target_type:
43+
if c in _TARGET_TYPE_NAME_CHARS:
44+
result += c.lower()
45+
in_replace = False
46+
elif not in_replace:
47+
result += '_'
48+
in_replace = True
49+
return result

pyocd/target/builtin/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,9 @@
124124

125125
## @brief Dictionary of all builtin targets.
126126
#
127-
# @note Target type names must be all lowercase and use _underscores_ instead of dashes. The code in Board
128-
# automatically converts dashes in user-supplied target type names to underscores.
127+
# @note Target type names must be a valid C identifier, normalised to all lowercase, using _underscores_
128+
# instead of dashes punctuation. See pyocd.target.normalise_target_type_name() for the code that
129+
# normalises user-provided target type names for comparison with these.
129130
BUILTIN_TARGETS = {
130131
'mps3_an522': target_MPS3_AN522.AN522,
131132
'mps3_an540': target_MPS3_AN540.AN540,

pyocd/target/pack/pack_target.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from .. import TARGET
2525
from ...coresight.coresight_target import CoreSightTarget
2626
from ...debug.svd.loader import SVDFile
27+
from .. import normalise_target_type_name
2728

2829
if TYPE_CHECKING:
2930
from zipfile import ZipFile
@@ -96,10 +97,10 @@ def populate_target(device_name: str) -> None:
9697
device part number is used to find the target to populate. If multiple packs are installed
9798
that provide the same part numbers, all matching targets will be populated.
9899
"""
99-
device_name = device_name.lower()
100+
device_name = normalise_target_type_name(device_name)
100101
targets = ManagedPacks.get_installed_targets()
101102
for dev in targets:
102-
if device_name == dev.part_number.lower():
103+
if device_name == normalise_target_type_name(dev.part_number):
103104
PackTargets.populate_device(dev)
104105

105106
if CPM_AVAILABLE:
@@ -171,7 +172,7 @@ def _generate_pack_target_class(dev: CmsisPackDevice) -> Optional[type]:
171172
superklass = PackTargets._find_family_class(dev)
172173

173174
# Replace spaces and dashes with underscores on the new target subclass name.
174-
subclassName = dev.part_number.replace(' ', '_').replace('-', '_')
175+
subclassName = normalise_target_type_name(dev.part_number).capitalize()
175176

176177
# Create a new subclass for this target.
177178
targetClass = type(subclassName, (superklass,), {
@@ -197,7 +198,7 @@ def populate_device(dev: CmsisPackDevice) -> None:
197198
tgt = PackTargets._generate_pack_target_class(dev)
198199
if tgt is None:
199200
return
200-
part = dev.part_number.lower().replace("-", "_")
201+
part = normalise_target_type_name(dev.part_number)
201202

202203
# Make sure there isn't a duplicate target name.
203204
if part not in TARGET:
@@ -249,7 +250,7 @@ def is_pack_target_available(target_name: str, session: "Session") -> bool:
249250
if session.options['pack'] is not None:
250251
target_types = []
251252
def collect_target_type(dev: CmsisPackDevice) -> None:
252-
part = dev.part_number.lower().replace("-", "_")
253+
part = normalise_target_type_name(dev.part_number)
253254
target_types.append(part)
254255
PackTargets.process_targets_from_pack(session.options['pack'], collect_target_type)
255256
return target_name.lower() in target_types

test/unit/test_cmdline.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# pyOCD debugger
22
# Copyright (c) 2015,2018-2019 Arm Limited
3+
# Copyright (c) 2022 Chris Reed
34
# SPDX-License-Identifier: Apache-2.0
45
#
56
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,6 +25,7 @@
2425
convert_session_options,
2526
)
2627
from pyocd.core.target import Target
28+
from pyocd.target import normalise_target_type_name
2729

2830
class TestSplitCommandLine(object):
2931
def test_split(self):
@@ -141,4 +143,19 @@ def test_str(self):
141143
# Valid
142144
assert convert_session_options(['test_binary=abc']) == {'test_binary': 'abc'}
143145

146+
class TestTargetTypeNormalisation:
147+
def test_passthrough(self):
148+
assert normalise_target_type_name("foobar") == "foobar"
149+
assert normalise_target_type_name("foo123bar") == "foo123bar"
150+
assert normalise_target_type_name("foo_bar") == "foo_bar"
151+
152+
def test_lower(self):
153+
assert normalise_target_type_name("TARGET") == "target"
154+
assert normalise_target_type_name("BIGtarget") == "bigtarget"
155+
assert normalise_target_type_name("someTARGET123") == "sometarget123"
156+
157+
def test_trans(self):
158+
assert normalise_target_type_name("foo-bar") == "foo_bar"
159+
assert normalise_target_type_name("foo--bar") == "foo_bar"
160+
assert normalise_target_type_name("foo!@#$%^&*()x") == "foo_x"
144161

0 commit comments

Comments
 (0)