Skip to content

Commit f3dc8a6

Browse files
Merge pull request #4103 from andrewleech/extra_targets
Add extra_targets.json support to build tools
2 parents cbfb234 + 4491d2e commit f3dc8a6

File tree

8 files changed

+167
-30
lines changed

8 files changed

+167
-30
lines changed

tools/build.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from tools.targets import TARGET_NAMES, TARGET_MAP
3333
from tools.options import get_default_options_parser
3434
from tools.options import extract_profile
35+
from tools.options import extract_mcus
3536
from tools.build_api import build_library, build_mbed_libs, build_lib
3637
from tools.build_api import mcu_toolchain_matrix
3738
from tools.build_api import print_build_results
@@ -134,7 +135,7 @@
134135

135136

136137
# Get target list
137-
targets = options.mcu if options.mcu else TARGET_NAMES
138+
targets = extract_mcus(parser, options) if options.mcu else TARGET_NAMES
138139

139140
# Get toolchains list
140141
toolchains = options.tool if options.tool else TOOLCHAINS

tools/get_config.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
from tools.utils import args_error
2828
from tools.options import get_default_options_parser
29+
from tools.options import extract_mcus
2930
from tools.build_api import get_config
3031
from config import Config
3132
from utils import argparse_filestring_type
@@ -49,7 +50,7 @@
4950
# Target
5051
if options.mcu is None :
5152
args_error(parser, "argument -m/--mcu is required")
52-
target = options.mcu[0]
53+
target = extract_mcus(parser, options)[0]
5354

5455
# Toolchain
5556
if options.tool is None:

tools/make.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from tools.targets import TARGET_MAP
4343
from tools.options import get_default_options_parser
4444
from tools.options import extract_profile
45+
from tools.options import extract_mcus
4546
from tools.build_api import build_project
4647
from tools.build_api import mcu_toolchain_matrix
4748
from tools.build_api import mcu_toolchain_list
@@ -200,7 +201,7 @@
200201
# Target
201202
if options.mcu is None :
202203
args_error(parser, "argument -m/--mcu is required")
203-
mcu = options.mcu[0]
204+
mcu = extract_mcus(parser, options)[0]
204205

205206
# Toolchain
206207
if options.tool is None:

tools/options.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
from json import load
1818
from os.path import join, dirname
1919
from os import listdir
20-
from argparse import ArgumentParser
20+
from argparse import ArgumentParser, ArgumentTypeError
2121
from tools.toolchains import TOOLCHAINS
22-
from tools.targets import TARGET_NAMES
22+
from tools.targets import TARGET_NAMES, Target, update_target_data
2323
from tools.utils import argparse_force_uppercase_type, \
2424
argparse_lowercase_hyphen_type, argparse_many, \
2525
argparse_filestring_type, args_error, argparse_profile_filestring_type,\
@@ -47,10 +47,7 @@ def get_default_options_parser(add_clean=True, add_options=True,
4747
parser.add_argument("-m", "--mcu",
4848
help=("build for the given MCU (%s)" %
4949
', '.join(targetnames)),
50-
metavar="MCU",
51-
type=argparse_many(
52-
argparse_force_uppercase_type(
53-
targetnames, "MCU")))
50+
metavar="MCU")
5451

5552
parser.add_argument("-t", "--tool",
5653
help=("build using the given TOOLCHAIN (%s)" %
@@ -130,3 +127,19 @@ def mcu_is_enabled(parser, mcu):
130127
"See https://developer.mbed.org/platforms/Renesas-GR-PEACH/#important-notice "
131128
"for more information") % (mcu, mcu))
132129
return True
130+
131+
def extract_mcus(parser, options):
132+
try:
133+
if options.source_dir:
134+
for source_dir in options.source_dir:
135+
Target.add_extra_targets(source_dir)
136+
update_target_data()
137+
except KeyError:
138+
pass
139+
targetnames = TARGET_NAMES
140+
targetnames.sort()
141+
try:
142+
return argparse_many(argparse_force_uppercase_type(targetnames, "MCU"))(options.mcu)
143+
except ArgumentTypeError as exc:
144+
args_error(parser, "argument -m/--mcu: {}".format(str(exc)))
145+

tools/project.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from tools.utils import argparse_force_lowercase_type
2121
from tools.utils import argparse_force_uppercase_type
2222
from tools.utils import print_large_string
23-
from tools.options import extract_profile, list_profiles
23+
from tools.options import extract_profile, list_profiles, extract_mcus
2424

2525
def setup_project(ide, target, program=None, source_dir=None, build=None, export_path=None):
2626
"""Generate a name, if not provided, and find dependencies
@@ -247,7 +247,8 @@ def main():
247247
profile = extract_profile(parser, options, toolchain_name, fallback="debug")
248248
if options.clean:
249249
rmtree(BUILD_DIR)
250-
export(options.mcu, options.ide, build=options.build,
250+
mcu = extract_mcus(parser, options)[0]
251+
export(mcu, options.ide, build=options.build,
251252
src=options.source_dir, macros=options.macros,
252253
project_id=options.program, zip_proj=zip_proj,
253254
build_profile=profile, app_config=options.app_config)

tools/targets/__init__.py

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import inspect
2323
import sys
2424
from copy import copy
25-
from collections import namedtuple
25+
from collections import namedtuple, Mapping
2626
from tools.targets.LPC import patch
2727
from tools.paths import TOOLS_BOOTLOADERS
2828
from tools.utils import json_file_to_dict
@@ -125,18 +125,38 @@ class Target(namedtuple("Target", "name json_data resolution_order resolution_or
125125
# Current/new location of the 'targets.json' file
126126
__targets_json_location = None
127127

128+
# Extra custom targets files
129+
__extra_target_json_files = []
130+
128131
@staticmethod
129132
@cached
130133
def get_json_target_data():
131134
"""Load the description of JSON target data"""
132-
return json_file_to_dict(Target.__targets_json_location or
133-
Target.__targets_json_location_default)
135+
targets = json_file_to_dict(Target.__targets_json_location or
136+
Target.__targets_json_location_default)
137+
138+
for extra_target in Target.__extra_target_json_files:
139+
for k, v in json_file_to_dict(extra_target).iteritems():
140+
if k in targets:
141+
print 'WARNING: Custom target "%s" cannot replace existing target.' % k
142+
else:
143+
targets[k] = v
144+
145+
return targets
146+
147+
@staticmethod
148+
def add_extra_targets(source_dir):
149+
extra_targets_file = os.path.join(source_dir, "custom_targets.json")
150+
if os.path.exists(extra_targets_file):
151+
Target.__extra_target_json_files.append(extra_targets_file)
152+
CACHES.clear()
134153

135154
@staticmethod
136155
def set_targets_json_location(location=None):
137156
"""Set the location of the targets.json file"""
138157
Target.__targets_json_location = (location or
139158
Target.__targets_json_location_default)
159+
Target.__extra_target_json_files = []
140160
# Invalidate caches, since the location of the JSON file changed
141161
CACHES.clear()
142162

@@ -507,14 +527,20 @@ def binary_hook(t_self, resources, elf, binf):
507527
################################################################################
508528

509529
# Instantiate all public targets
510-
TARGETS = [Target.get_target(name) for name, value
511-
in Target.get_json_target_data().items()
512-
if value.get("public", True)]
530+
def update_target_data():
531+
TARGETS[:] = [Target.get_target(tgt) for tgt, obj
532+
in Target.get_json_target_data().items()
533+
if obj.get("public", True)]
534+
# Map each target name to its unique instance
535+
TARGET_MAP.clear()
536+
TARGET_MAP.update(dict([(tgt.name, tgt) for tgt in TARGETS]))
537+
TARGET_NAMES[:] = TARGET_MAP.keys()
513538

514-
# Map each target name to its unique instance
515-
TARGET_MAP = dict([(t.name, t) for t in TARGETS])
539+
TARGETS = []
540+
TARGET_MAP = dict()
541+
TARGET_NAMES = []
516542

517-
TARGET_NAMES = TARGET_MAP.keys()
543+
update_target_data()
518544

519545
# Some targets with different name have the same exporters
520546
EXPORT_MAP = {}
@@ -537,9 +563,5 @@ def set_targets_json_location(location=None):
537563
# re-initialization does not create new variables, it keeps the old ones
538564
# instead. This ensures compatibility with code that does
539565
# "from tools.targets import TARGET_NAMES"
540-
TARGETS[:] = [Target.get_target(tgt) for tgt, obj
541-
in Target.get_json_target_data().items()
542-
if obj.get("public", True)]
543-
TARGET_MAP.clear()
544-
TARGET_MAP.update(dict([(tgt.name, tgt) for tgt in TARGETS]))
545-
TARGET_NAMES[:] = TARGET_MAP.keys()
566+
update_target_data()
567+

tools/test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
from tools.config import ConfigException
3030
from tools.test_api import test_path_to_name, find_tests, print_tests, build_tests, test_spec_from_test_builds
31-
from tools.options import get_default_options_parser, extract_profile
31+
from tools.options import get_default_options_parser, extract_profile, extract_mcus
3232
from tools.build_api import build_project, build_library
3333
from tools.build_api import print_build_memory_usage
3434
from tools.build_api import merge_build_data
@@ -114,7 +114,7 @@
114114
# Target
115115
if options.mcu is None :
116116
args_error(parser, "argument -m/--mcu is required")
117-
mcu = options.mcu[0]
117+
mcu = extract_mcus(parser, options)[0]
118118

119119
# Toolchain
120120
if options.tool is None:

tools/test/targets/target_test.py

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,23 @@
1515
See the License for the specific language governing permissions and
1616
limitations under the License.
1717
"""
18-
18+
import os
1919
import sys
20+
import shutil
21+
import tempfile
2022
from os.path import join, abspath, dirname
23+
from contextlib import contextmanager
2124
import unittest
2225

2326
# Be sure that the tools directory is in the search path
27+
2428
ROOT = abspath(join(dirname(__file__), "..", "..", ".."))
2529
sys.path.insert(0, ROOT)
2630

27-
from tools.targets import TARGETS
31+
from tools.targets import TARGETS, TARGET_MAP, Target, update_target_data
2832
from tools.arm_pack_manager import Cache
2933

34+
3035
class TestTargets(unittest.TestCase):
3136

3237
def test_device_name(self):
@@ -39,5 +44,98 @@ def test_device_name(self):
3944
"Target %s contains invalid device_name %s" %
4045
(target.name, target.device_name))
4146

47+
@contextmanager
48+
def temp_target_file(self, extra_target, json_filename='custom_targets.json'):
49+
"""Create an extra targets temp file in a context manager"""
50+
tempdir = tempfile.mkdtemp()
51+
try:
52+
targetfile = os.path.join(tempdir, json_filename)
53+
with open(targetfile, 'w') as f:
54+
f.write(extra_target)
55+
yield tempdir
56+
finally:
57+
# Reset extra targets
58+
Target.set_targets_json_location()
59+
# Delete temp files
60+
shutil.rmtree(tempdir)
61+
62+
def test_add_extra_targets(self):
63+
"""Search for extra targets json in a source folder"""
64+
test_target_json = """
65+
{
66+
"Test_Target": {
67+
"inherits": ["Target"]
68+
}
69+
}
70+
"""
71+
with self.temp_target_file(test_target_json) as source_dir:
72+
Target.add_extra_targets(source_dir=source_dir)
73+
update_target_data()
74+
75+
assert 'Test_Target' in TARGET_MAP
76+
assert TARGET_MAP['Test_Target'].core is None, \
77+
"attributes should be inherited from Target"
78+
79+
def test_modify_existing_target(self):
80+
"""Set default targets file, then override base Target definition"""
81+
initial_target_json = """
82+
{
83+
"Target": {
84+
"core": null,
85+
"default_toolchain": "ARM",
86+
"supported_toolchains": null,
87+
"extra_labels": [],
88+
"is_disk_virtual": false,
89+
"macros": [],
90+
"device_has": [],
91+
"features": [],
92+
"detect_code": [],
93+
"public": false,
94+
"default_lib": "std",
95+
"bootloader_supported": false
96+
},
97+
"Test_Target": {
98+
"inherits": ["Target"],
99+
"core": "Cortex-M4",
100+
"supported_toolchains": ["ARM"]
101+
}
102+
}"""
103+
104+
test_target_json = """
105+
{
106+
"Target": {
107+
"core": "Cortex-M0",
108+
"default_toolchain": "GCC_ARM",
109+
"supported_toolchains": null,
110+
"extra_labels": [],
111+
"is_disk_virtual": false,
112+
"macros": [],
113+
"device_has": [],
114+
"features": [],
115+
"detect_code": [],
116+
"public": false,
117+
"default_lib": "std",
118+
"bootloader_supported": true
119+
}
120+
}
121+
"""
122+
123+
with self.temp_target_file(initial_target_json, json_filename="targets.json") as targets_dir:
124+
Target.set_targets_json_location(os.path.join(targets_dir, "targets.json"))
125+
update_target_data()
126+
assert TARGET_MAP["Test_Target"].core == "Cortex-M4"
127+
assert TARGET_MAP["Test_Target"].default_toolchain == 'ARM'
128+
assert TARGET_MAP["Test_Target"].bootloader_supported == False
129+
130+
with self.temp_target_file(test_target_json) as source_dir:
131+
Target.add_extra_targets(source_dir=source_dir)
132+
update_target_data()
133+
134+
assert TARGET_MAP["Test_Target"].core == "Cortex-M4"
135+
# The existing target should not be modified by custom targets
136+
assert TARGET_MAP["Test_Target"].default_toolchain != 'GCC_ARM'
137+
assert TARGET_MAP["Test_Target"].bootloader_supported != True
138+
139+
42140
if __name__ == '__main__':
43141
unittest.main()

0 commit comments

Comments
 (0)