takashi kadono / Mbed OS Nucleo_446

Dependencies:   ssd1331

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers __init__.py Source File

__init__.py

00001 """
00002 mbed SDK
00003 Copyright (c) 2011-2016 ARM Limited
00004 
00005 Licensed under the Apache License, Version 2.0 (the "License");
00006 you may not use this file except in compliance with the License.
00007 You may obtain a copy of the License at
00008 
00009 http://www.apache.org/licenses/LICENSE-2.0
00010 
00011 Unless required by applicable law or agreed to in writing, software
00012 distributed under the License is distributed on an "AS IS" BASIS,
00013 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00014 See the License for the specific language governing permissions and
00015 limitations under the License.
00016 """
00017 from __future__ import print_function
00018 
00019 import os
00020 import binascii
00021 import struct
00022 import shutil
00023 import inspect
00024 import sys
00025 from copy import copy
00026 from inspect import getmro
00027 from collections import namedtuple, Mapping
00028 from tools.resources import FileType
00029 from tools.targets.LPC import patch
00030 from tools.paths import TOOLS_BOOTLOADERS
00031 from tools.utils import json_file_to_dict
00032 
00033 __all__ = ["target", "TARGETS", "TARGET_MAP", "TARGET_NAMES", "CORE_LABELS",
00034            "CORE_ARCH", "HookError", "generate_py_target", "Target",
00035            "CUMULATIVE_ATTRIBUTES", "get_resolution_order"]
00036 
00037 CORE_LABELS = {
00038     "Cortex-M0": ["M0", "CORTEX_M", "LIKE_CORTEX_M0", "CORTEX"],
00039     "Cortex-M0+": ["M0P", "CORTEX_M", "LIKE_CORTEX_M0", "CORTEX"],
00040     "Cortex-M1": ["M1", "CORTEX_M", "LIKE_CORTEX_M1", "CORTEX"],
00041     "Cortex-M3": ["M3", "CORTEX_M", "LIKE_CORTEX_M3", "CORTEX"],
00042     "Cortex-M4": ["M4", "CORTEX_M", "RTOS_M4_M7", "LIKE_CORTEX_M4", "CORTEX"],
00043     "Cortex-M4F": ["M4", "CORTEX_M", "RTOS_M4_M7", "LIKE_CORTEX_M4", "CORTEX"],
00044     "Cortex-M7": ["M7", "CORTEX_M", "RTOS_M4_M7", "LIKE_CORTEX_M7", "CORTEX"],
00045     "Cortex-M7F": ["M7", "CORTEX_M", "RTOS_M4_M7", "LIKE_CORTEX_M7", "CORTEX"],
00046     "Cortex-M7FD": ["M7", "CORTEX_M", "RTOS_M4_M7", "LIKE_CORTEX_M7", "CORTEX"],
00047     "Cortex-A9": ["A9", "CORTEX_A", "LIKE_CORTEX_A9", "CORTEX"],
00048     "Cortex-M23": ["M23", "CORTEX_M", "LIKE_CORTEX_M23", "CORTEX"],
00049     "Cortex-M23-NS": ["M23", "M23_NS", "CORTEX_M", "LIKE_CORTEX_M23", "CORTEX"],
00050     "Cortex-M33": ["M33", "CORTEX_M", "LIKE_CORTEX_M33", "CORTEX"],
00051     "Cortex-M33-NS": ["M33", "M33_NS", "CORTEX_M", "LIKE_CORTEX_M33", "CORTEX"]
00052 }
00053 
00054 CORE_ARCH = {
00055     "Cortex-M0": 6,
00056     "Cortex-M0+": 6,
00057     "Cortex-M1": 6,
00058     "Cortex-M3": 7,
00059     "Cortex-M4": 7,
00060     "Cortex-M4F": 7,
00061     "Cortex-M7": 7,
00062     "Cortex-M7F": 7,
00063     "Cortex-M7FD": 7,
00064     "Cortex-A9": 7,
00065     "Cortex-M23": 8,
00066     "Cortex-M23-NS": 8,
00067     "Cortex-M33": 8,
00068     "Cortex-M33-NS": 8,
00069 }
00070 
00071 ################################################################################
00072 # Generic Target class that reads and interprets the data in targets.json
00073 
00074 class HookError(Exception):
00075     """ A simple class that represents all the exceptions associated with
00076     hooking
00077     """
00078     pass
00079 
00080 CACHES = {}
00081 def cached (func):
00082     """A simple decorator used for automatically caching data returned by a
00083     function
00084     """
00085     def wrapper(*args, **kwargs):
00086         """The wrapped function itself"""
00087         if (func.__name__, args) not in CACHES:
00088             CACHES[(func.__name__, args)] = func(*args, **kwargs)
00089         return CACHES[(func.__name__, args)]
00090     return wrapper
00091 
00092 
00093 # Cumulative attributes can have values appended to them, so they
00094 # need to be computed differently than regular attributes
00095 CUMULATIVE_ATTRIBUTES = ['extra_labels', 'macros', 'device_has', 'features', 'components']
00096 
00097 
00098 def get_resolution_order (json_data, target_name, order, level=0):
00099     """ Return the order in which target descriptions are searched for
00100     attributes. This mimics the Python 2.2 method resolution order, which
00101     is what the old targets.py module used. For more details, check
00102     http://makina-corpus.com/blog/metier/2014/python-tutorial-understanding-python-mro-class-search-path
00103     The resolution order contains (name, level) tuples, where "name" is the
00104     name of the class and "level" is the level in the inheritance hierarchy
00105     (the target itself is at level 0, its first parent at level 1, its
00106     parent's parent at level 2 and so on)
00107     """
00108     # the resolution order can't contain duplicate target names
00109     if target_name not in [l[0] for l in order]:
00110         order.append((target_name, level))
00111     parents = json_data[target_name].get("inherits", [])
00112     for par in parents:
00113         order = get_resolution_order(json_data, par, order, level + 1)
00114     return order
00115 
00116 
00117 def target (name, json_data):
00118     """Construct a target object"""
00119     resolution_order = get_resolution_order(json_data, name, [])
00120     resolution_order_names = [tgt for tgt, _ in resolution_order]
00121     return Target(name=name,
00122                   json_data={key: value for key, value in json_data.items()
00123                              if key in resolution_order_names},
00124                   resolution_order=resolution_order,
00125                   resolution_order_names=resolution_order_names)
00126 
00127 def generate_py_target (new_targets, name):
00128     """Add one or more new target(s) represented as a Python dictionary
00129     in 'new_targets'. It is an error to add a target with a name that
00130     already exists.
00131     """
00132     base_targets = Target.get_json_target_data()
00133     for new_target in new_targets.keys():
00134         if new_target in base_targets:
00135             raise Exception("Attempt to add target '%s' that already exists"
00136                             % new_target)
00137     total_data = {}
00138     total_data.update(new_targets)
00139     total_data.update(base_targets)
00140     return target(name, total_data)
00141 
00142 class Target (namedtuple("Target", "name json_data resolution_order resolution_order_names")):
00143     """An object to represent a Target (MCU/Board)"""
00144 
00145     # Default location of the 'targets.json' file
00146     __targets_json_location_default = os.path.join(
00147         os.path.dirname(os.path.abspath(__file__)), '..', '..', 'targets', 'targets.json')
00148 
00149     # Current/new location of the 'targets.json' file
00150     __targets_json_location = None
00151 
00152     # Extra custom targets files
00153     __extra_target_json_files = []
00154 
00155     @staticmethod
00156     @cached
00157     def get_json_target_data ():
00158         """Load the description of JSON target data"""
00159         targets = json_file_to_dict(Target.__targets_json_location or
00160                                     Target.__targets_json_location_default)
00161 
00162         for extra_target in Target.__extra_target_json_files:
00163             for k, v in json_file_to_dict(extra_target).items():
00164                 if k in targets:
00165                     print('WARNING: Custom target "%s" cannot replace existing '
00166                           'target.' % k)
00167                 else:
00168                     targets[k] = v
00169 
00170         return targets
00171 
00172     @staticmethod
00173     def add_extra_targets(source_dir):
00174         extra_targets_file = os.path.join(source_dir, "custom_targets.json")
00175         if os.path.exists(extra_targets_file):
00176             Target.__extra_target_json_files.append(extra_targets_file)
00177             CACHES.clear()
00178 
00179     @staticmethod
00180     def set_targets_json_location (location=None):
00181         """Set the location of the targets.json file"""
00182         Target.__targets_json_location = (location or
00183                                           Target.__targets_json_location_default)
00184         Target.__extra_target_json_files = []
00185         # Invalidate caches, since the location of the JSON file changed
00186         CACHES.clear()
00187 
00188     @staticmethod
00189     @cached
00190     def get_module_data ():
00191         """Get the members of this module using Python's "inspect" module"""
00192         return dict([(m[0], m[1]) for m in
00193                      inspect.getmembers(sys.modules[__name__])])
00194 
00195     @staticmethod
00196     def __add_paths_to_progen(data):
00197         """Modify the exporter specification ("progen") by changing all
00198         "template" keys to full paths
00199         """
00200         out = {}
00201         for key, val in data.items():
00202             if isinstance(val, dict):
00203                 out[key] = Target.__add_paths_to_progen(val)
00204             elif key == "template":
00205                 out[key] = [os.path.join(os.path.dirname(__file__), 'export', v)
00206                             for v in val]
00207             else:
00208                 out[key] = val
00209         return out
00210 
00211     def __getattr_cumulative(self, attrname):
00212         """Look for the attribute in the class and its parents, as defined by
00213         the resolution order
00214         """
00215         tdata = self.json_data
00216         # For a cumulative attribute, figure out when it was defined the
00217         # last time (in attribute resolution order) then follow the "_add"
00218         # and "_remove" data fields
00219         for idx, tgt in enumerate(self.resolution_order):
00220             # the attribute was defined at this level in the resolution
00221             # order
00222             if attrname in tdata[tgt[0]]:
00223                 def_idx = idx
00224                 break
00225         else:
00226             return []
00227         # Get the starting value of the attribute
00228         starting_value = (tdata[self.resolution_order[def_idx][0]][attrname]
00229                           or [])[:]
00230         # Traverse the resolution list in high inheritance to low
00231         # inheritance level, left to right order to figure out all the
00232         # other classes that change the definition by adding or removing
00233         # elements
00234         for idx in range(self.resolution_order[def_idx][1] - 1, -1, -1):
00235             same_level_targets = [tar[0] for tar in self.resolution_order
00236                                   if tar[1] == idx]
00237             for tar in same_level_targets:
00238                 data = tdata[tar]
00239                 # Do we have anything to add ?
00240                 if (attrname + "_add") in data:
00241                     starting_value.extend(data[attrname + "_add"])
00242                 # Do we have anything to remove ?
00243                 if (attrname + "_remove") in data:
00244                     # Macros can be defined either without a value (MACRO)
00245                     # or with a value (MACRO=10). When removing, we specify
00246                     # only the name of the macro, without the value. So we
00247                     # need to create a mapping between the macro name and
00248                     # its value. This will work for extra_labels and other
00249                     # type of arrays as well, since they fall into the
00250                     # "macros without a value" category (simple definitions
00251                     # without a value).
00252                     name_def_map = {}
00253                     for crtv in starting_value:
00254                         if crtv.find('=') != -1:
00255                             temp = crtv.split('=')
00256                             if len(temp) != 2:
00257                                 raise ValueError(
00258                                     "Invalid macro definition '%s'" % crtv)
00259                             name_def_map[temp[0]] = crtv
00260                         else:
00261                             name_def_map[crtv] = crtv
00262                     for element in data[attrname + "_remove"]:
00263                         if element not in name_def_map:
00264                             raise ValueError(
00265                                 ("Unable to remove '%s' in '%s.%s' since "
00266                                  % (element, self.name, attrname)) +
00267                                 "it doesn't exist")
00268                         starting_value.remove(name_def_map[element])
00269         return starting_value
00270 
00271     def __getattr_helper(self, attrname):
00272         """Compute the value of a given target attribute"""
00273         if attrname in CUMULATIVE_ATTRIBUTES:
00274             return self.__getattr_cumulative (attrname)
00275         else:
00276             tdata = self.json_data
00277             starting_value = None
00278             for tgt in self.resolution_order:
00279                 data = tdata[tgt[0]]
00280                 try:
00281                     return data[attrname]
00282                 except KeyError:
00283                     pass
00284             else: # Attribute not found
00285                 raise AttributeError(
00286                     "Attribute '%s' not found in target '%s'"
00287                     % (attrname, self.name))
00288 
00289     def __getattr__ (self, attrname):
00290         """ Return the value of an attribute. This function only computes the
00291         attribute's value once, then adds it to the instance attributes (in
00292         __dict__), so the next time it is returned directly
00293         """
00294         result = self.__getattr_helper (attrname)
00295         self.__dict__[attrname] = result
00296         return result
00297 
00298     @staticmethod
00299     @cached
00300     def get_target (target_name):
00301         """ Return the target instance starting from the target name """
00302         return target(target_name, Target.get_json_target_data())
00303 
00304 
00305     @property
00306     def program_cycle_s (self):
00307         """Special override for program_cycle_s as it's default value depends
00308         upon is_disk_virtual
00309         """
00310         try:
00311             return self.__getattr__ ("program_cycle_s")
00312         except AttributeError:
00313             return 4 if self.is_disk_virtual else 1.5
00314 
00315     @property
00316     def labels (self):
00317         """Get all possible labels for this target"""
00318         names = copy(self.resolution_order_names)
00319         if "Target" in names:
00320             names.remove("Target")
00321         labels = (names + CORE_LABELS[self.core] + self.extra_labels)
00322         return labels
00323 
00324     def init_hooks (self, hook, toolchain):
00325         """Initialize the post-build hooks for a toolchain. For now, this
00326         function only allows "post binary" hooks (hooks that are executed
00327         after the binary image is extracted from the executable file)
00328 
00329         Positional Arguments:
00330         hook - the hook object to add post-binary-hooks to
00331         toolchain - the toolchain object for inspection
00332         """
00333 
00334         # If there's no hook, simply return
00335         try:
00336             hook_data = self.post_binary_hook
00337         except AttributeError:
00338             return
00339         # A hook was found. The hook's name is in the format
00340         # "classname.functionname"
00341         temp = hook_data["function"].split(".")
00342         if len(temp) != 2:
00343             raise HookError(
00344                 ("Invalid format for hook '%s' in target '%s'"
00345                  % (hook_data["function"], self.name)) +
00346                 " (must be 'class_name.function_name')")
00347         class_name, function_name = temp
00348         # "class_name" must refer to a class in this file, so check if the
00349         # class exists
00350         mdata = self.get_module_data ()
00351         if class_name not in mdata or \
00352            not inspect.isclass(mdata[class_name]):
00353             raise HookError(
00354                 ("Class '%s' required by '%s' in target '%s'"
00355                  % (class_name, hook_data["function"], self.name)) +
00356                 " not found in targets.py")
00357         # "function_name" must refer to a static function inside class
00358         # "class_name"
00359         cls = mdata[class_name]
00360         if (not hasattr(cls, function_name)) or \
00361            (not inspect.isfunction(getattr(cls, function_name))):
00362             raise HookError(
00363                 ("Static function '%s' " % function_name) +
00364                 ("required by '%s' " % hook_data["function"]) +
00365                 ("in target '%s' " % self.name) +
00366                 ("not found in class '%s'" %  class_name))
00367         # Check if the hook specification also has toolchain restrictions
00368         toolchain_restrictions = set(hook_data.get("toolchains", []))
00369         toolchain_labels = set(c.__name__ for c in getmro(toolchain.__class__))
00370         if toolchain_restrictions and \
00371            not toolchain_labels.intersection(toolchain_restrictions):
00372             return
00373         # Finally, hook the requested function
00374         hook.hook_add_binary("post", getattr(cls, function_name))
00375 
00376 ################################################################################
00377 # Target specific code goes in this section
00378 # This code can be invoked from the target description using the
00379 # "post_binary_hook" key
00380 
00381 class LPCTargetCode (object):
00382     """General LPC Target patching code"""
00383     @staticmethod
00384     def lpc_patch (t_self, resources, elf, binf):
00385         """Patch an elf file"""
00386         t_self.notify.debug("LPC Patch: %s" % os.path.split(binf)[1])
00387         patch(binf)
00388 
00389 class LPC4088Code (object):
00390     """Code specific to the LPC4088"""
00391     @staticmethod
00392     def binary_hook (t_self, resources, elf, binf):
00393         """Hook to be run after an elf file is built"""
00394         if not os.path.isdir(binf):
00395             # Regular binary file, nothing to do
00396             LPCTargetCode.lpc_patch(t_self, resources, elf, binf)
00397             return
00398         outbin = open(binf + ".temp", "wb")
00399         partf = open(os.path.join(binf, "ER_IROM1"), "rb")
00400         # Pad the fist part (internal flash) with 0xFF to 512k
00401         data = partf.read()
00402         outbin.write(data)
00403         outbin.write('\xFF' * (512*1024 - len(data)))
00404         partf.close()
00405         # Read and append the second part (external flash) in chunks of fixed
00406         # size
00407         chunksize = 128 * 1024
00408         partf = open(os.path.join(binf, "ER_IROM2"), "rb")
00409         while True:
00410             data = partf.read(chunksize)
00411             outbin.write(data)
00412             if len(data) < chunksize:
00413                 break
00414         partf.close()
00415         outbin.close()
00416         # Remove the directory with the binary parts and rename the temporary
00417         # file to 'binf'
00418         shutil.rmtree(binf, True)
00419         os.rename(binf + '.temp', binf)
00420         t_self.notify.debug("Generated custom binary file (internal flash + SPIFI)")
00421         LPCTargetCode.lpc_patch(t_self, resources, elf, binf)
00422 
00423 class TEENSY3_1Code (object):
00424     """Hooks for the TEENSY3.1"""
00425     @staticmethod
00426     def binary_hook (t_self, resources, elf, binf):
00427         """Hook that is run after elf is generated"""
00428         # This function is referenced by old versions of targets.json and should
00429         # be kept for backwards compatibility.
00430         pass
00431 
00432 class MTSCode(object):
00433     """Generic MTS code"""
00434     @staticmethod
00435     def _combine_bins_helper(target_name, binf):
00436         """combine bins with the bootloader for a particular target"""
00437         loader = os.path.join(TOOLS_BOOTLOADERS, target_name, "bootloader.bin")
00438         target = binf + ".tmp"
00439         if not os.path.exists(loader):
00440             print("Can't find bootloader binary: " + loader)
00441             return
00442         outbin = open(target, 'w+b')
00443         part = open(loader, 'rb')
00444         data = part.read()
00445         outbin.write(data)
00446         outbin.write('\xFF' * (64*1024 - len(data)))
00447         part.close()
00448         part = open(binf, 'rb')
00449         data = part.read()
00450         outbin.write(data)
00451         part.close()
00452         outbin.seek(0, 0)
00453         data = outbin.read()
00454         outbin.seek(0, 1)
00455         crc = struct.pack('<I', binascii.crc32(data) & 0xFFFFFFFF)
00456         outbin.write(crc)
00457         outbin.close()
00458         os.remove(binf)
00459         os.rename(target, binf)
00460 
00461     @staticmethod
00462     def combine_bins_mts_dot (t_self, resources, elf, binf):
00463         """A hook for the MTS MDOT"""
00464         MTSCode._combine_bins_helper("MTS_MDOT_F411RE", binf)
00465 
00466     @staticmethod
00467     def combine_bins_mts_dragonfly (t_self, resources, elf, binf):
00468         """A hoof for the MTS Dragonfly"""
00469         MTSCode._combine_bins_helper("MTS_DRAGONFLY_F411RE", binf)
00470 
00471     @staticmethod
00472     def combine_bins_mtb_mts_dragonfly (t_self, resources, elf, binf):
00473         """A hook for the MTB MTS Dragonfly"""
00474         MTSCode._combine_bins_helper("MTB_MTS_DRAGONFLY", binf)
00475 
00476 class MCU_NRF51Code (object):
00477     """NRF51 Hooks"""
00478     @staticmethod
00479     def binary_hook (t_self, resources, _, binf):
00480         """Hook that merges the soft device with the bin file"""
00481         # Scan to find the actual paths of soft device
00482         sdf = None
00483         for softdevice_and_offset_entry\
00484             in t_self.target.EXPECTED_SOFTDEVICES_WITH_OFFSETS:
00485             for hexf in resources.get_file_paths(FileType.HEX):
00486                 if hexf.find(softdevice_and_offset_entry['name']) != -1:
00487                     t_self.notify.debug("SoftDevice file found %s."
00488                                         % softdevice_and_offset_entry['name'])
00489                     sdf = hexf
00490 
00491                 if sdf is not None:
00492                     break
00493             if sdf is not None:
00494                 break
00495 
00496         if sdf is None:
00497             t_self.notify.debug("Hex file not found. Aborting.")
00498             return
00499 
00500         # Look for bootloader file that matches this soft device or bootloader
00501         # override image
00502         blf = None
00503         if t_self.target.MERGE_BOOTLOADER is True:
00504             for hexf in resources.get_file_paths(FileType.HEX):
00505                 if hexf.find(t_self.target.OVERRIDE_BOOTLOADER_FILENAME) != -1:
00506                     t_self.notify.debug("Bootloader file found %s."
00507                                         % t_self.target.OVERRIDE_BOOTLOADER_FILENAME)
00508                     blf = hexf
00509                     break
00510                 elif hexf.find(softdevice_and_offset_entry['boot']) != -1:
00511                     t_self.notify.debug("Bootloader file found %s."
00512                                         % softdevice_and_offset_entry['boot'])
00513                     blf = hexf
00514                     break
00515 
00516         # Merge user code with softdevice
00517         from intelhex import IntelHex
00518         binh = IntelHex()
00519         _, ext = os.path.splitext(binf)
00520         if ext == ".hex":
00521             binh.loadhex(binf)
00522         elif ext == ".bin":
00523             binh.loadbin(binf, softdevice_and_offset_entry['offset'])
00524 
00525         if t_self.target.MERGE_SOFT_DEVICE is True:
00526             t_self.notify.debug("Merge SoftDevice file %s"
00527                                 % softdevice_and_offset_entry['name'])
00528             sdh = IntelHex(sdf)
00529             sdh.start_addr = None
00530             binh.merge(sdh)
00531 
00532         if t_self.target.MERGE_BOOTLOADER is True and blf is not None:
00533             t_self.notify.debug("Merge BootLoader file %s" % blf)
00534             blh = IntelHex(blf)
00535             blh.start_addr = None
00536             binh.merge(blh)
00537 
00538         with open(binf.replace(".bin", ".hex"), "w") as fileout:
00539             binh.write_hex_file(fileout, write_start_addr=False)
00540 
00541 class NCS36510TargetCode:
00542     @staticmethod
00543     def ncs36510_addfib(t_self, resources, elf, binf):
00544         from tools.targets.NCS import add_fib_at_start
00545         print("binf ", binf)
00546         add_fib_at_start(binf[:-4])
00547 
00548 class RTL8195ACode :
00549     """RTL8195A Hooks"""
00550     @staticmethod
00551     def binary_hook(t_self, resources, elf, binf):
00552         from tools.targets.REALTEK_RTL8195AM import rtl8195a_elf2bin
00553         rtl8195a_elf2bin(t_self, elf, binf)
00554 ################################################################################
00555 
00556 # Instantiate all public targets
00557 def update_target_data():
00558     TARGETS[:] = [Target.get_target(tgt) for tgt, obj
00559                   in Target.get_json_target_data().items()
00560                   if obj.get("public", True)]
00561     # Map each target name to its unique instance
00562     TARGET_MAP.clear()
00563     TARGET_MAP.update(dict([(tgt.name, tgt) for tgt in TARGETS]))
00564     TARGET_NAMES[:] = TARGET_MAP.keys()
00565 
00566 TARGETS = []
00567 TARGET_MAP = dict()
00568 TARGET_NAMES = []
00569 
00570 update_target_data()
00571 
00572 # Some targets with different name have the same exporters
00573 EXPORT_MAP = {}
00574 
00575 # Detection APIs
00576 def get_target_detect_codes ():
00577     """ Returns dictionary mapping detect_code -> platform_name
00578     """
00579     result = {}
00580     for tgt in TARGETS:
00581         for detect_code in tgt.detect_code:
00582             result[detect_code] = tgt.name
00583     return result
00584 
00585 def set_targets_json_location (location=None):
00586     """Sets the location of the JSON file that contains the targets"""
00587     # First instruct Target about the new location
00588     Target.set_targets_json_location(location)
00589     # Then re-initialize TARGETS, TARGET_MAP and TARGET_NAMES. The
00590     # re-initialization does not create new variables, it keeps the old ones
00591     # instead. This ensures compatibility with code that does
00592     # "from tools.targets import TARGET_NAMES"
00593     update_target_data()
00594