diff --git a/tools/build_api.py b/tools/build_api.py index ceb0014156..4ed3be5c0f 100755 --- a/tools/build_api.py +++ b/tools/build_api.py @@ -27,7 +27,6 @@ from os.path import join, exists, dirname, basename, abspath, normpath, splitext from os.path import relpath from os import linesep, remove, makedirs from time import time -from intelhex import IntelHex from json import load, dump from jinja2 import FileSystemLoader from jinja2.environment import Environment @@ -35,7 +34,7 @@ from jinja2.environment import Environment from .arm_pack_manager import Cache from .utils import (mkdir, run_cmd, run_cmd_ext, NotSupportedException, ToolException, InvalidReleaseTargetException, - intelhex_offset, integer, generate_update_filename, copy_when_different) + copy_when_different) from .paths import (MBED_CMSIS_PATH, MBED_TARGETS_PATH, MBED_LIBRARIES, MBED_HEADER, MBED_DRIVERS, MBED_PLATFORM, MBED_HAL, MBED_CONFIG_FILE, MBED_LIBRARIES_DRIVERS, @@ -393,124 +392,6 @@ def prepare_toolchain(src_paths, build_dir, target, toolchain_name, return toolchain -def _printihex(ihex): - import pprint - pprint.PrettyPrinter().pprint(ihex.todict()) - -def _real_region_size(region): - try: - part = intelhex_offset(region.filename, offset=region.start) - return (part.maxaddr() - part.minaddr()) + 1 - except AttributeError: - return region.size - - -def _fill_header(region_list, current_region): - """Fill an application header region - - This is done it three steps: - * Fill the whole region with zeros - * Fill const, timestamp and size entries with their data - * Fill the digests using this header as the header region - """ - region_dict = {r.name: r for r in region_list} - header = IntelHex() - header.puts(current_region.start, b'\x00' * current_region.size) - start = current_region.start - for member in current_region.filename: - _, type, subtype, data = member - member_size = Config.header_member_size(member) - if type == "const": - fmt = { - "8le": ">B", "16le": "H", "32be": ">L", "64be": ">Q" - }[subtype] - header.puts(start, struct.pack(fmt, integer(data, 0))) - elif type == "timestamp": - fmt = {"32le": "L", "64be": ">Q"}[subtype] - header.puts(start, struct.pack(fmt, int(time()))) - elif type == "size": - fmt = {"32le": "L", "64be": ">Q"}[subtype] - size = sum(_real_region_size(region_dict[r]) for r in data) - header.puts(start, struct.pack(fmt, size)) - elif type == "digest": - if data == "header": - ih = header[:start] - else: - ih = intelhex_offset(region_dict[data].filename, offset=region_dict[data].start) - if subtype.startswith("CRCITT32"): - fmt = {"CRCITT32be": ">L", "CRCITT32le": " region.size: - raise ToolException("Contents of region %s does not fit" - % region.name) - merged_list.append(region.filename) - merged.merge(part) - elif region.filename in merged_list: - notify.info(" Skipping %s as it is merged previously" % (region.name)) - - # Hex file can have gaps, so no padding needed. While other formats may - # need padding. Iterate through segments and pad the gaps. - if format != ".hex": - # begin patching from the end of the first segment - _, begin = merged.segments()[0] - for start, stop in merged.segments()[1:]: - pad_size = start - begin - merged.puts(begin, padding * pad_size) - begin = stop + 1 - - if not exists(dirname(destination)): - makedirs(dirname(destination)) - notify.info("Space used after regions merged: 0x%x" % - (merged.maxaddr() - merged.minaddr() + 1)) - merged.tofile(destination, format=format.strip(".")) - UPDATE_WHITELIST = ( "application", @@ -605,27 +486,7 @@ def build_project(src_paths, build_path, target, toolchain_name, objects = toolchain.compile_sources(resources, sorted(resources.get_file_paths(FileType.INC_DIR))) resources.add_files_to_type(FileType.OBJECT, objects) - # Link Program - if toolchain.config.has_regions: - binary, _ = toolchain.link_program(resources, build_path, name + "_application") - region_list = list(toolchain.config.regions) - region_list = [r._replace(filename=binary) if r.active else r - for r in region_list] - res = "%s.%s" % (join(build_path, name), - getattr(toolchain.target, "OUTPUT_EXT", "bin")) - merge_region_list(region_list, res, notify, toolchain.config) - update_regions = [ - r for r in region_list if r.name in UPDATE_WHITELIST - ] - if update_regions: - update_res = join(build_path, generate_update_filename(name, toolchain.target)) - merge_region_list(update_regions, update_res, notify, toolchain.config) - res = (res, update_res) - else: - res = (res, None) - else: - res, _ = toolchain.link_program(resources, build_path, name) - res = (res, None) + res = toolchain.link_program(resources, build_path, name) into_dir, extra_artifacts = toolchain.config.deliver_into() if into_dir: diff --git a/tools/hooks.py b/tools/hooks.py deleted file mode 100644 index f3b5265702..0000000000 --- a/tools/hooks.py +++ /dev/null @@ -1,214 +0,0 @@ -""" Configurable hooks in the build system. Can be used by various platforms -to customize the build process. -""" - -################################################################################ -# Hooks for the various parts of the build process - -# Internal mapping of hooks per tool -_HOOKS = {} - -# Internal mapping of running hooks -_RUNNING_HOOKS = {} - -# Available hook types -_HOOK_TYPES = ["binary", "compile", "link", "assemble"] - -# Available hook steps -_HOOK_STEPS = ["pre", "replace", "post"] - -# Hook the given function. Use this function as a decorator -def hook_tool(function): - """Decorate a function as a tool that may be hooked""" - tool = function.__name__ - tool_flag = "_" + tool + "_done" - def wrapper(t_self, *args, **kwargs): - """The hooked function itself""" - # if a hook for this tool is already running, it's most likely - # coming from a derived class, so don't hook the super class version - if _RUNNING_HOOKS.get(tool, False): - return function(t_self, *args, **kwargs) - _RUNNING_HOOKS[tool] = True - # If this tool isn't hooked, return original function - if tool not in _HOOKS: - res = function(t_self, *args, **kwargs) - _RUNNING_HOOKS[tool] = False - return res - tooldesc = _HOOKS[tool] - setattr(t_self, tool_flag, False) - # If there is a replace hook, execute the replacement instead - if "replace" in tooldesc: - res = tooldesc["replace"](t_self, *args, **kwargs) - # If the replacement has set the "done" flag, exit now - # Otherwise continue as usual - if getattr(t_self, tool_flag, False): - _RUNNING_HOOKS[tool] = False - return res - # Execute pre-function before main function if specified - if "pre" in tooldesc: - tooldesc["pre"](t_self, *args, **kwargs) - # Execute the main function now - res = function(t_self, *args, **kwargs) - # Execute post-function after main function if specified - if "post" in tooldesc: - post_res = tooldesc["post"](t_self, *args, **kwargs) - _RUNNING_HOOKS[tool] = False - return post_res or res - else: - _RUNNING_HOOKS[tool] = False - return res - return wrapper - -class Hook(object): - """A compiler class that may be hooked""" - def __init__(self, target, toolchain): - _HOOKS.clear() - self._cmdline_hooks = {} - self.toolchain = toolchain - target.init_hooks(self, toolchain) - - # Hook various functions directly - @staticmethod - def _hook_add(hook_type, hook_step, function): - """Add a hook to a compile function - - Positional arguments: - hook_type - one of the _HOOK_TYPES - hook_step - one of the _HOOK_STEPS - function - the function to add to the list of hooks - """ - if hook_type not in _HOOK_TYPES or hook_step not in _HOOK_STEPS: - return False - if hook_type not in _HOOKS: - _HOOKS[hook_type] = {} - _HOOKS[hook_type][hook_step] = function - return True - - def hook_add_compiler(self, hook_step, function): - """Add a hook to the compiler - - Positional Arguments: - hook_step - one of the _HOOK_STEPS - function - the function to add to the list of hooks - """ - return self._hook_add("compile", hook_step, function) - - def hook_add_linker(self, hook_step, function): - """Add a hook to the linker - - Positional Arguments: - hook_step - one of the _HOOK_STEPS - function - the function to add to the list of hooks - """ - return self._hook_add("link", hook_step, function) - - def hook_add_assembler(self, hook_step, function): - """Add a hook to the assemble - - Positional Arguments: - hook_step - one of the _HOOK_STEPS - function - the function to add to the list of hooks - """ - return self._hook_add("assemble", hook_step, function) - - def hook_add_binary(self, hook_step, function): - """Add a hook to the elf to binary tool - - Positional Arguments: - hook_step - one of the _HOOK_STEPS - function - the function to add to the list of hooks - """ - return self._hook_add("binary", hook_step, function) - - # Hook command lines - def _hook_cmdline(self, hook_type, function): - """Add a hook to a command line function - - Positional arguments: - hook_type - one of the _HOOK_TYPES - function - the function to add to the list of hooks - """ - if hook_type not in _HOOK_TYPES: - return False - self._cmdline_hooks[hook_type] = function - return True - - def hook_cmdline_compiler(self, function): - """Add a hook to the compiler command line - - Positional arguments: - function - the function to call - """ - return self._hook_cmdline("compile", function) - - def hook_cmdline_linker(self, function): - """Add a hook to the linker command line - - Positional arguments: - function - the function to call - """ - return self._hook_cmdline("link", function) - - def hook_cmdline_assembler(self, function): - """Add a hook to the assembler command line - - Positional arguments: - function - the function to call - """ - return self._hook_cmdline("assemble", function) - - def hook_cmdline_binary(self, function): - """Add a hook to the elf to bin tool command line - - Positional arguments: - function - the function to call - """ - return self._hook_cmdline("binary", function) - - # Return the command line after applying the hook - def _get_cmdline(self, hook_type, cmdline): - """Get the command line after running all hooks - - Positional arguments: - hook_type - one of the _HOOK_TYPES - cmdline - the initial command line - """ - if hook_type in self._cmdline_hooks: - cmdline = self._cmdline_hooks[hook_type]( - self.toolchain.__class__.__name__, cmdline) - return cmdline - - def get_cmdline_compiler(self, cmdline): - """Get the compiler command line after running all hooks - - Positional arguments: - cmdline - the initial command line - """ - return self._get_cmdline("compile", cmdline) - - def get_cmdline_linker(self, cmdline): - """Get the linker command line after running all hooks - - Positional arguments: - cmdline - the initial command line - """ - return self._get_cmdline("link", cmdline) - - def get_cmdline_assembler(self, cmdline): - """Get the assmebler command line after running all hooks - - Positional arguments: - cmdline - the initial command line - """ - return self._get_cmdline("assemble", cmdline) - - def get_cmdline_binary(self, cmdline): - """Get the binary command line after running all hooks - - Positional arguments: - cmdline - the initial command line - """ - return self._get_cmdline("binary", cmdline) - -################################################################################ - diff --git a/tools/regions.py b/tools/regions.py new file mode 100644 index 0000000000..3cc8eb8e58 --- /dev/null +++ b/tools/regions.py @@ -0,0 +1,175 @@ +# mbed SDK +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2011-2013 ARM Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Utilities for working with region lists. +""" + +import hashlib +import struct +import zlib +from time import time +from os.path import splitext, exists, dirname +from os import makedirs +from .config import Config +from .utils import ( + ToolException, + intelhex_offset, + integer +) +from intelhex import IntelHex + +UPDATE_WHITELIST = ( + "application" +) + + +def _printihex(ihex): + import pprint + pprint.PrettyPrinter().pprint(ihex.todict()) + + +def _real_region_size(region): + try: + part = intelhex_offset(region.filename, offset=region.start) + return (part.maxaddr() - part.minaddr()) + 1 + except AttributeError: + return region.size + + +def _fill_header(region_list, current_region): + """Fill an application header region + + This is done it three steps: + * Fill the whole region with zeros + * Fill const, timestamp and size entries with their data + * Fill the digests using this header as the header region + """ + region_dict = {r.name: r for r in region_list} + header = IntelHex() + header.puts(current_region.start, b'\x00' * current_region.size) + start = current_region.start + for member in current_region.filename: + _, type, subtype, data = member + if type == "const": + fmt = { + "8le": ">B", "16le": "H", "32be": ">L", "64be": ">Q" + }[subtype] + header.puts(start, struct.pack(fmt, integer(data, 0))) + elif type == "timestamp": + fmt = {"32le": "L", "64be": ">Q"}[subtype] + header.puts(start, struct.pack(fmt, int(time()))) + elif type == "size": + fmt = {"32le": "L", "64be": ">Q"}[subtype] + size = sum(_real_region_size(region_dict[r]) for r in data) + header.puts(start, struct.pack(fmt, size)) + elif type == "digest": + if data == "header": + ih = header[:start] + else: + ih = intelhex_offset( + region_dict[data].filename, + offset=region_dict[data].start + ) + if subtype.startswith("CRCITT32"): + fmt = {"CRCITT32be": ">L", "CRCITT32le": " region.size: + raise ToolException( + "Contents of region %s does not fit" % region.name + ) + merged_list.append(region.filename) + merged.merge(part) + elif region.filename in merged_list: + notify.info( + " Skipping %s as it is merged previously" % (region.name) + ) + + # Hex file can have gaps, so no padding needed. While other formats may + # need padding. Iterate through segments and pad the gaps. + if format != ".hex": + # begin patching from the end of the first segment + _, begin = merged.segments()[0] + for start, stop in merged.segments()[1:]: + pad_size = start - begin + merged.puts(begin, padding * pad_size) + begin = stop + 1 + + if not exists(dirname(destination)): + makedirs(dirname(destination)) + notify.info("Space used after regions merged: 0x%x" % + (merged.maxaddr() - merged.minaddr() + 1)) + merged.tofile(destination, format=format.strip(".")) diff --git a/tools/targets/__init__.py b/tools/targets/__init__.py index 6c7fbc642c..451116338d 100644 --- a/tools/targets/__init__.py +++ b/tools/targets/__init__.py @@ -355,7 +355,7 @@ class Target(namedtuple("Target", "name json_data resolution_order resolution_or def is_PSA_non_secure_target(self): return 'NSPE_Target' in self.labels - def init_hooks(self, hook, toolchain): + def get_post_build_hook(self, toolchain): """Initialize the post-build hooks for a toolchain. For now, this function only allows "post binary" hooks (hooks that are executed after the binary image is extracted from the executable file) @@ -404,8 +404,7 @@ class Target(namedtuple("Target", "name json_data resolution_order resolution_or if toolchain_restrictions and \ not toolchain_labels.intersection(toolchain_restrictions): return - # Finally, hook the requested function - hook.hook_add_binary("post", getattr(cls, function_name)) + return getattr(cls, function_name) ################################################################################ # Target specific code goes in this section diff --git a/tools/test/build_api/build_api_test.py b/tools/test/build_api/build_api_test.py index be26b6e16d..34a157e2bf 100755 --- a/tools/test/build_api/build_api_test.py +++ b/tools/test/build_api/build_api_test.py @@ -18,7 +18,8 @@ limitations under the License. import unittest from collections import namedtuple from mock import patch, MagicMock -from tools.build_api import prepare_toolchain, build_project, build_library, merge_region_list +from tools.build_api import prepare_toolchain, build_project, build_library +from tools.regions import merge_region_list from tools.resources import Resources from tools.toolchains import TOOLCHAINS from tools.notifier.mock import MockNotifier @@ -30,7 +31,7 @@ from intelhex import IntelHex Tests for build_api.py """ make_mock_target = namedtuple( - "Target", "init_hooks name features core supported_toolchains build_tools_metadata") + "Target", "get_post_build_hook name features core supported_toolchains build_tools_metadata") #Add ARMC5 to the supported_toolchains list as ARMC5 actually refers ARM Compiler 5 and is needed by ARM/ARM_STD classes when it checks for supported toolchains TOOLCHAINS.add("ARMC5") #Make a mock build_tools_metadata @@ -65,7 +66,6 @@ class BuildApiTests(unittest.TestCase): @patch('tools.toolchains.mbedToolchain.need_update', side_effect=[i % 2 for i in range(3000)]) @patch('os.mkdir') - @patch('tools.toolchains.exists', return_value=True) @patch('tools.toolchains.mbedToolchain.dump_build_profile') @patch('tools.utils.run_cmd', return_value=(b'', b'', 0)) def test_always_complete_build(self, *_): @@ -94,7 +94,7 @@ class BuildApiTests(unittest.TestCase): :return: """ app_config = "app_config" - mock_target = make_mock_target(lambda _, __ : None, + mock_target = make_mock_target(lambda _ : None, "Junk", [], "Cortex-M3", TOOLCHAINS, mock_build_tools_metadata) mock_config_init.return_value = namedtuple( "Config", "target has_regions name")(mock_target, False, None) @@ -113,7 +113,7 @@ class BuildApiTests(unittest.TestCase): :param mock_config_init: mock of Config __init__ :return: """ - mock_target = make_mock_target(lambda _, __ : None, + mock_target = make_mock_target(lambda _ : None, "Junk", [], "Cortex-M3", TOOLCHAINS, mock_build_tools_metadata) mock_config_init.return_value = namedtuple( "Config", "target has_regions name")(mock_target, False, None) @@ -246,7 +246,7 @@ class BuildApiTests(unittest.TestCase): self.assertEqual(args[1]['app_config'], None, "prepare_toolchain was called with an incorrect app_config") - @patch('tools.build_api.intelhex_offset') + @patch('tools.regions.intelhex_offset') @patch('tools.config') def test_merge_region_no_fit(self, mock_config, mock_intelhex_offset): """ diff --git a/tools/toolchains/__init__.py b/tools/toolchains/__init__.py index 4c6ee457bf..aba8347548 100755 --- a/tools/toolchains/__init__.py +++ b/tools/toolchains/__init__.py @@ -16,1192 +16,18 @@ limitations under the License. """ from __future__ import print_function, division, absolute_import -import re -import sys -import json -from os import stat, walk, getcwd, sep, remove, getenv, rename, remove -from copy import copy -from time import time, sleep -from shutil import copyfile -from os.path import (join, splitext, exists, relpath, dirname, basename, split, - abspath, isfile, isdir, normcase) -from inspect import getmro -from copy import deepcopy -from collections import namedtuple -from abc import ABCMeta, abstractmethod -from distutils.spawn import find_executable -from multiprocessing import Pool, cpu_count -from hashlib import md5 - -from ..utils import (run_cmd, mkdir, rel_path, ToolException, - NotSupportedException, split_path, compile_worker) -from ..settings import MBED_ORG_USER, PRINT_COMPILER_OUTPUT_AS_LINK -from .. import hooks -from ..notifier.term import TerminalNotifier -from ..resources import FileType -from ..memap import MemapParser -from ..config import (ConfigException, RAM_ALL_MEMORIES, ROM_ALL_MEMORIES) -from ..settings import COMPARE_FIXED - - -#Disables multiprocessing if set to higher number than the host machine CPUs -CPU_COUNT_MIN = 1 -CPU_COEF = 1 - -class mbedToolchain: - OFFICIALLY_SUPPORTED = False - - # Verbose logging - VERBOSE = True - - # Compile C files as CPP - COMPILE_C_AS_CPP = False - - # Response files for compiling, includes, linking and archiving. - # Not needed on posix systems where the typical arg limit is 2 megabytes - RESPONSE_FILES = True - - CORTEX_SYMBOLS = { - "Cortex-M0" : ["__CORTEX_M0", "ARM_MATH_CM0", "__CMSIS_RTOS", "__MBED_CMSIS_RTOS_CM"], - "Cortex-M0+": ["__CORTEX_M0PLUS", "ARM_MATH_CM0PLUS", "__CMSIS_RTOS", "__MBED_CMSIS_RTOS_CM"], - "Cortex-M1" : ["__CORTEX_M3", "ARM_MATH_CM1", "__CMSIS_RTOS", "__MBED_CMSIS_RTOS_CM"], - "Cortex-M3" : ["__CORTEX_M3", "ARM_MATH_CM3", "__CMSIS_RTOS", "__MBED_CMSIS_RTOS_CM"], - "Cortex-M4" : ["__CORTEX_M4", "ARM_MATH_CM4", "__CMSIS_RTOS", "__MBED_CMSIS_RTOS_CM"], - "Cortex-M4F" : ["__CORTEX_M4", "ARM_MATH_CM4", "__FPU_PRESENT=1", "__CMSIS_RTOS", "__MBED_CMSIS_RTOS_CM"], - "Cortex-M7" : ["__CORTEX_M7", "ARM_MATH_CM7", "__CMSIS_RTOS", "__MBED_CMSIS_RTOS_CM"], - "Cortex-M7F" : ["__CORTEX_M7", "ARM_MATH_CM7", "__FPU_PRESENT=1", "__CMSIS_RTOS", "__MBED_CMSIS_RTOS_CM"], - "Cortex-M7FD" : ["__CORTEX_M7", "ARM_MATH_CM7", "__FPU_PRESENT=1", "__CMSIS_RTOS", "__MBED_CMSIS_RTOS_CM"], - "Cortex-A9" : ["__CORTEX_A9", "ARM_MATH_CA9", "__FPU_PRESENT", "__CMSIS_RTOS", "__EVAL", "__MBED_CMSIS_RTOS_CA9"], - "Cortex-M23-NS": ["__CORTEX_M23", "ARM_MATH_ARMV8MBL", "DOMAIN_NS=1", "__CMSIS_RTOS", "__MBED_CMSIS_RTOS_CM"], - "Cortex-M23": ["__CORTEX_M23", "ARM_MATH_ARMV8MBL", "__CMSIS_RTOS", "__MBED_CMSIS_RTOS_CM"], - "Cortex-M33-NS": ["__CORTEX_M33", "ARM_MATH_ARMV8MML", "DOMAIN_NS=1", "__CMSIS_RTOS", "__MBED_CMSIS_RTOS_CM"], - "Cortex-M33": ["__CORTEX_M33", "ARM_MATH_ARMV8MML", "__CMSIS_RTOS", "__MBED_CMSIS_RTOS_CM"], - "Cortex-M33F-NS": ["__CORTEX_M33", "ARM_MATH_ARMV8MML", "DOMAIN_NS=1", "__FPU_PRESENT=1U", "__CMSIS_RTOS", "__MBED_CMSIS_RTOS_CM"], - "Cortex-M33F": ["__CORTEX_M33", "ARM_MATH_ARMV8MML", "__FPU_PRESENT=1U", "__CMSIS_RTOS", "__MBED_CMSIS_RTOS_CM"], - "Cortex-M33FE-NS": ["__CORTEX_M33", "ARM_MATH_ARMV8MML", "DOMAIN_NS=1", "__FPU_PRESENT=1U", "__CMSIS_RTOS", "__MBED_CMSIS_RTOS_CM", "__DSP_PRESENT=1U"], - "Cortex-M33FE": ["__CORTEX_M33", "ARM_MATH_ARMV8MML", "__FPU_PRESENT=1U", "__CMSIS_RTOS", "__MBED_CMSIS_RTOS_CM", "__DSP_PRESENT=1U"], - } - - MBED_CONFIG_FILE_NAME="mbed_config.h" - - PROFILE_FILE_NAME = ".profile" - - __metaclass__ = ABCMeta - - profile_template = {'common':[], 'c':[], 'cxx':[], 'asm':[], 'ld':[]} - - def __init__(self, target, notify=None, macros=None, build_profile=None, - build_dir=None): - self.target = target - self.name = self.__class__.__name__ - - # compile/assemble/link/binary hooks - self.hook = hooks.Hook(target, self) - - # Toolchain flags - self.flags = deepcopy(build_profile or self.profile_template) - - # System libraries provided by the toolchain - self.sys_libs = [] - - # User-defined macros - self.macros = macros or [] - - # Macros generated from toolchain and target rules/features - self.asm_symbols = None - self.cxx_symbols = None - - # Labels generated from toolchain and target rules/features (used for selective build) - self.labels = None - - # This will hold the initialized config object - self.config = None - - # This will hold the configuration data (as returned by Config.get_config_data()) - self.config_data = None - - # This will hold the location of the configuration file or None if there's no configuration available - self.config_file = None - - # Call guard for "get_config_data" (see the comments of get_config_data for details) - self.config_processed = False - - # Non-incremental compile - self.build_all = False - - # Build output dir - self.build_dir = abspath(build_dir) if PRINT_COMPILER_OUTPUT_AS_LINK else build_dir - self.timestamp = getenv("MBED_BUILD_TIMESTAMP",time()) - - # Number of concurrent build jobs. 0 means auto (based on host system cores) - self.jobs = 0 - - - # Output notify function - # This function is passed all events, and expected to handle notification of the - # user, emit the events to a log, etc. - # The API for all notify methods passed into the notify parameter is as follows: - # def notify(Event, Silent) - # Where *Event* is a dict representing the toolchain event that was generated - # e.g.: a compile succeeded, or a warning was emitted by the compiler - # or an application was linked - # *Silent* is a boolean - if notify: - self.notify = notify - else: - self.notify = TerminalNotifier() - - - # Stats cache is used to reduce the amount of IO requests to stat - # header files during dependency change. See need_update() - self.stat_cache = {} - - # Used by the mbed Online Build System to build in chrooted environment - self.CHROOT = None - - # Call post __init__() hooks before the ARM/GCC_ARM/IAR toolchain __init__() takes over - self.init() - - # Used for post __init__() hooks - # THIS METHOD IS BEING OVERRIDDEN BY THE MBED ONLINE BUILD SYSTEM - # ANY CHANGE OF PARAMETERS OR RETURN VALUES WILL BREAK COMPATIBILITY - def init(self): - return True - - def get_output(self): - return self.notifier.get_output() - - def get_symbols(self, for_asm=False): - if for_asm: - if self.asm_symbols is None: - self.asm_symbols = [] - - # Cortex CPU symbols - if self.target.core in mbedToolchain.CORTEX_SYMBOLS: - self.asm_symbols.extend(mbedToolchain.CORTEX_SYMBOLS[self.target.core]) - - # Add target's symbols - self.asm_symbols += self.target.macros - # Add extra symbols passed via 'macros' parameter - self.asm_symbols += self.macros - return list(set(self.asm_symbols)) # Return only unique symbols - else: - if self.cxx_symbols is None: - # Target and Toolchain symbols - labels = self.get_labels() - self.cxx_symbols = ["TARGET_%s" % t for t in labels['TARGET']] - self.cxx_symbols.extend(["TOOLCHAIN_%s" % t for t in labels['TOOLCHAIN']]) - - # Cortex CPU symbols - if self.target.core in mbedToolchain.CORTEX_SYMBOLS: - self.cxx_symbols.extend(mbedToolchain.CORTEX_SYMBOLS[self.target.core]) - - # Symbols defined by the on-line build.system - self.cxx_symbols.extend(['MBED_BUILD_TIMESTAMP=%s' % self.timestamp, 'TARGET_LIKE_MBED', '__MBED__=1']) - if MBED_ORG_USER: - self.cxx_symbols.append('MBED_USERNAME=' + MBED_ORG_USER) - - # Add target's name - self.cxx_symbols += ["TARGET_NAME=" + self.target.name] - # Add target's symbols - self.cxx_symbols += self.target.macros - # Add target's hardware - self.cxx_symbols += ["DEVICE_" + data + "=1" for data in self.target.device_has] - # Add target's features - self.cxx_symbols += ["FEATURE_" + data + "=1" for data in self.target.features] - # Add target's components - self.cxx_symbols += ["COMPONENT_" + data + "=1" for data in self.target.components] - # Add extra symbols passed via 'macros' parameter - self.cxx_symbols += self.macros - - # Form factor variables - if hasattr(self.target, 'supported_form_factors'): - self.cxx_symbols.extend(["TARGET_FF_%s" % t for t in self.target.supported_form_factors]) - - return list(set(self.cxx_symbols)) # Return only unique symbols - - # Extend the internal list of macros - def add_macros(self, new_macros): - self.macros.extend(new_macros) - - def get_labels(self): - if self.labels is None: - toolchain_labels = self._get_toolchain_labels() - self.labels = { - 'TARGET': self.target.labels, - 'FEATURE': self.target.features, - 'COMPONENT': self.target.components, - 'TOOLCHAIN': toolchain_labels - } - - # This is a policy decision and it should /really/ be in the config system - # ATM it's here for backward compatibility - if ((("-g" in self.flags['common'] or "-g3" in self.flags['common']) and - "-O0" in self.flags['common']) or - ("-r" in self.flags['common'] and - "-On" in self.flags['common'])): - self.labels['TARGET'].append("DEBUG") - else: - self.labels['TARGET'].append("RELEASE") - return self.labels - - def _get_toolchain_labels(self): - toolchain_labels = [c.__name__ for c in getmro(self.__class__)] - toolchain_labels.remove('mbedToolchain') - toolchain_labels.remove('object') - return toolchain_labels - - - # Determine whether a source file needs updating/compiling - def need_update(self, target, dependencies): - if self.build_all: - return True - - if not exists(target): - return True - - target_mod_time = stat(target).st_mtime - - for d in dependencies: - # Some objects are not provided with full path and here we do not have - # information about the library paths. Safe option: assume an update - if not d or not exists(d): - return True - - if d not in self.stat_cache: - self.stat_cache[d] = stat(d).st_mtime - - if self.stat_cache[d] >= target_mod_time: - return True - - return False - - - def scan_repository(self, path): - resources = [] - - for root, dirs, files in walk(path): - # Remove ignored directories - for d in copy(dirs): - if d == '.' or d == '..': - dirs.remove(d) - - for file in files: - file_path = join(root, file) - resources.append(file_path) - - return resources - - def copy_files(self, files_paths, trg_path, resources=None): - # Handle a single file - if not isinstance(files_paths, list): - files_paths = [files_paths] - - for dest, source in files_paths: - target = join(trg_path, dest) - if (target != source) and (self.need_update(target, [source])): - self.progress("copy", dest) - mkdir(dirname(target)) - copyfile(source, target) - - # THIS METHOD IS BEING OVERRIDDEN BY THE MBED ONLINE BUILD SYSTEM - # ANY CHANGE OF PARAMETERS OR RETURN VALUES WILL BREAK COMPATIBILITY - def relative_object_path(self, build_path, file_ref): - source_dir, name, _ = split_path(file_ref.name) - - obj_dir = relpath(join(build_path, source_dir)) - if obj_dir is not self.prev_dir: - self.prev_dir = obj_dir - mkdir(obj_dir) - return join(obj_dir, name + '.o') - - def make_option_file(self, options, naming=".options_{}.txt"): - """ Generate a via file for a pile of defines - ARM, GCC, IAR cross compatible - """ - to_write = " ".join(options).encode('utf-8') - new_md5 = md5(to_write).hexdigest() - via_file = join(self.build_dir, naming.format(new_md5)) - try: - with open(via_file, "r") as fd: - old_md5 = md5(fd.read().encode('utf-8')).hexdigest() - except IOError: - old_md5 = None - if old_md5 != new_md5: - with open(via_file, "wb") as fd: - fd.write(to_write) - return via_file - - def get_inc_file(self, includes): - """Generate a via file for all includes. - ARM, GCC, IAR cross compatible - """ - cmd_list = ("\"-I{}\"".format(c.replace("\\", "/")) for c in includes if c) - if self.CHROOT: - cmd_list = (c.replace(self.CHROOT, '') for c in cmd_list) - return self.make_option_file(list(cmd_list), naming=".includes_{}.txt") - - def get_link_file(self, cmd): - """Generate a via file for all objects when linking. - ARM, GCC, IAR cross compatible - """ - cmd_list = (c.replace("\\", "/") for c in cmd if c) - if self.CHROOT: - cmd_list = (c.replace(self.CHROOT, '') for c in cmd_list) - return self.make_option_file(list(cmd_list), naming=".link_options.txt") - - def get_arch_file(self, objects): - """ Generate a via file for all objects when archiving. - ARM, GCC, IAR cross compatible - """ - cmd_list = (c.replace("\\", "/") for c in objects if c) - return self.make_option_file(list(cmd_list), ".archive_files.txt") - - # THIS METHOD IS BEING CALLED BY THE MBED ONLINE BUILD SYSTEM - # ANY CHANGE OF PARAMETERS OR RETURN VALUES WILL BREAK COMPATIBILITY - def compile_sources(self, resources, inc_dirs=None): - # Web IDE progress bar for project build - files_to_compile = ( - resources.get_file_refs(FileType.ASM_SRC) + - resources.get_file_refs(FileType.C_SRC) + - resources.get_file_refs(FileType.CPP_SRC) - ) - self.to_be_compiled = len(files_to_compile) - self.compiled = 0 - - self.notify.cc_verbose("Macros: "+' '.join(['-D%s' % s for s in self.get_symbols()])) - - inc_paths = resources.get_file_paths(FileType.INC_DIR) - if inc_dirs is not None: - if isinstance(inc_dirs, list): - inc_paths.extend(inc_dirs) - else: - inc_paths.append(inc_dirs) - # De-duplicate include paths - inc_paths = set(inc_paths) - # Sort include paths for consistency - inc_paths = sorted(set(inc_paths)) - # Unique id of all include paths - self.inc_md5 = md5(' '.join(inc_paths).encode('utf-8')).hexdigest() - - objects = [] - queue = [] - work_dir = getcwd() - self.prev_dir = None - - # Generate configuration header (this will update self.build_all if needed) - self.get_config_header() - self.dump_build_profile() - - # Sort compile queue for consistency - files_to_compile.sort() - for source in files_to_compile: - object = self.relative_object_path(self.build_dir, source) - - # Queue mode (multiprocessing) - commands = self.compile_command(source.path, object, inc_paths) - if commands is not None: - queue.append({ - 'source': source, - 'object': object, - 'commands': commands, - 'work_dir': work_dir, - 'chroot': self.CHROOT - }) - else: - self.compiled += 1 - objects.append(object) - - # Use queues/multiprocessing if cpu count is higher than setting - jobs = self.jobs if self.jobs else cpu_count() - if jobs > CPU_COUNT_MIN and len(queue) > jobs: - return self.compile_queue(queue, objects) - else: - return self.compile_seq(queue, objects) - - # Compile source files queue in sequential order - def compile_seq(self, queue, objects): - for item in queue: - result = compile_worker(item) - - self.compiled += 1 - self.progress("compile", item['source'].name, build_update=True) - for res in result['results']: - self.notify.cc_verbose("Compile: %s" % ' '.join(res['command']), result['source'].name) - self.compile_output([ - res['code'], - res['output'], - res['command'] - ]) - objects.append(result['object']) - return objects - - # Compile source files queue in parallel by creating pool of worker threads - def compile_queue(self, queue, objects): - jobs_count = int(self.jobs if self.jobs else cpu_count() * CPU_COEF) - p = Pool(processes=jobs_count) - - results = [] - for i in range(len(queue)): - results.append(p.apply_async(compile_worker, [queue[i]])) - p.close() - - itr = 0 - while len(results): - itr += 1 - if itr > 180000: - p.terminate() - p.join() - raise ToolException("Compile did not finish in 5 minutes") - - sleep(0.01) - pending = 0 - for r in results: - if r.ready(): - try: - result = r.get() - results.remove(r) - - self.compiled += 1 - self.progress("compile", result['source'].name, build_update=True) - for res in result['results']: - self.notify.cc_verbose("Compile: %s" % ' '.join(res['command']), result['source'].name) - self.compile_output([ - res['code'], - res['output'], - res['command'] - ]) - objects.append(result['object']) - except ToolException as err: - if p._taskqueue.queue: - p._taskqueue.queue.clear() - sleep(0.5) - p.terminate() - p.join() - raise ToolException(err) - else: - pending += 1 - if pending >= jobs_count: - break - - results = None - p.join() - - return objects - - # Determine the compile command based on type of source file - def compile_command(self, source, object, includes): - # Check dependencies - _, ext = splitext(source) - ext = ext.lower() - - source = abspath(source) if PRINT_COMPILER_OUTPUT_AS_LINK else source - - if ext == '.c' or ext == '.cpp' or ext == '.cc': - base, _ = splitext(object) - dep_path = base + '.d' - try: - deps = self.parse_dependencies(dep_path) if (exists(dep_path)) else [] - except (IOError, IndexError): - deps = [] - config_file = ([self.config.app_config_location] - if self.config.app_config_location else []) - deps.extend(config_file) - if ext != '.c' or self.COMPILE_C_AS_CPP: - deps.append(join(self.build_dir, self.PROFILE_FILE_NAME + "-cxx")) - else: - deps.append(join(self.build_dir, self.PROFILE_FILE_NAME + "-c")) - if len(deps) == 0 or self.need_update(object, deps): - if ext != '.c' or self.COMPILE_C_AS_CPP: - return self.compile_cpp(source, object, includes) - else: - return self.compile_c(source, object, includes) - elif ext == '.s': - deps = [source] - deps.append(join(self.build_dir, self.PROFILE_FILE_NAME + "-asm")) - if self.need_update(object, deps): - return self.assemble(source, object, includes) - else: - return False - - return None - - def parse_dependencies(self, dep_path): - """Parse the dependency information generated by the compiler. - - Positional arguments: - dep_path -- the path to a file generated by a previous run of the compiler - - Return value: - A list of all source files that the dependency file indicated were dependencies - - Side effects: - None - - Note: A default implementation is provided for make-like file formats - """ - dependencies = [] - buff = open(dep_path).readlines() - if buff: - buff[0] = re.sub('^(.*?)\: ', '', buff[0]) - for line in buff: - filename = line.replace('\\\n', '').strip() - if filename: - filename = filename.replace('\\ ', '\a') - dependencies.extend(((self.CHROOT if self.CHROOT else '') + - f.replace('\a', ' ')) - for f in filename.split(" ")) - return list(filter(None, dependencies)) - - def is_not_supported_error(self, output): - return "#error directive: [NOT_SUPPORTED]" in output - - @abstractmethod - def parse_output(self, output): - """Take in compiler output and extract sinlge line warnings and errors from it. - - Positional arguments: - output -- a string of all the messages emitted by a run of the compiler - - Return value: - None - - Side effects: - call self.cc_info or self.notify with a description of the event generated by the compiler - """ - raise NotImplemented - - def compile_output(self, output=[]): - _rc = output[0] - _stderr = output[1] - command = output[2] - - # Parse output for Warnings and Errors - self.parse_output(_stderr) - self.notify.debug("Return: %s"% _rc) - for error_line in _stderr.splitlines(): - self.notify.debug("Output: %s"% error_line) - - # Check return code - if _rc != 0: - if self.is_not_supported_error(_stderr): - raise NotSupportedException(_stderr) - else: - raise ToolException(_stderr) - - def build_library(self, objects, dir, name): - needed_update = False - lib = self.STD_LIB_NAME % name - fout = join(dir, lib) - if self.need_update(fout, objects): - self.notify.info("Library: %s" % lib) - self.archive(objects, fout) - needed_update = True - - return needed_update - - def link_program(self, r, tmp_path, name): - needed_update = False - ext = 'bin' - if hasattr(self.target, 'OUTPUT_EXT'): - ext = self.target.OUTPUT_EXT - - if hasattr(self.target, 'OUTPUT_NAMING'): - self.notify.var("binary_naming", self.target.OUTPUT_NAMING) - if self.target.OUTPUT_NAMING == "8.3": - name = name[0:8] - ext = ext[0:3] - - # Create destination directory - head, tail = split(name) - new_path = join(tmp_path, head) - mkdir(new_path) - - filename = name+'.'+ext - # Absolute path of the final linked file - full_path = join(tmp_path, filename) - elf = join(tmp_path, name + '.elf') - bin = None if ext == 'elf' else full_path - mapfile = join(tmp_path, name + '.map') - - objects = sorted(set(r.get_file_paths(FileType.OBJECT))) - config_file = ([self.config.app_config_location] - if self.config.app_config_location else []) - try: - linker_script = [path for _, path in r.get_file_refs(FileType.LD_SCRIPT) - if path.endswith(self.LINKER_EXT)][-1] - except IndexError: - raise NotSupportedException("No linker script found") - lib_dirs = r.get_file_paths(FileType.LIB_DIR) - libraries = [l for l in r.get_file_paths(FileType.LIB) - if l.endswith(self.LIBRARY_EXT)] - hex_files = r.get_file_paths(FileType.HEX) - dependencies = objects + libraries + [linker_script] + config_file + hex_files - dependencies.append(join(self.build_dir, self.PROFILE_FILE_NAME + "-ld")) - if self.need_update(elf, dependencies): - if not COMPARE_FIXED and exists(mapfile): - old_mapfile = "%s.old" % mapfile - if exists(old_mapfile): - remove(old_mapfile) - rename(mapfile, old_mapfile) - needed_update = True - self.progress("link", name) - self.link(elf, objects, libraries, lib_dirs, linker_script) - - if bin and self.need_update(bin, [elf]): - needed_update = True - self.progress("elf2bin", name) - self.binary(r, elf, bin) - - # Initialize memap and process map file. This doesn't generate output. - self.mem_stats(mapfile) - - self.notify.var("compile_succeded", True) - self.notify.var("binary", filename) - - return full_path, needed_update - - # THIS METHOD IS BEING OVERRIDDEN BY THE MBED ONLINE BUILD SYSTEM - # ANY CHANGE OF PARAMETERS OR RETURN VALUES WILL BREAK COMPATIBILITY - def default_cmd(self, command): - _stdout, _stderr, _rc = run_cmd(command, work_dir=getcwd(), chroot=self.CHROOT) - self.notify.debug("Return: %s"% _rc) - - for output_line in _stdout.splitlines(): - self.notify.debug("Output: %s"% output_line) - for error_line in _stderr.splitlines(): - self.notify.debug("Errors: %s"% error_line) - - if _rc != 0: - for line in _stderr.splitlines(): - self.notify.tool_error(line) - raise ToolException(_stderr) - - def progress(self, action, file, build_update=False): - if build_update: - percent = 100. * float(self.compiled) / float(self.to_be_compiled) - else: - percent = None - self.notify.progress(action, file, percent) - - # THIS METHOD IS BEING OVERRIDDEN BY THE MBED ONLINE BUILD SYSTEM - # ANY CHANGE OF PARAMETERS OR RETURN VALUES WILL BREAK COMPATIBILITY - def mem_stats(self, map): - """! Creates parser object - @param map Path to linker map file to parse and decode - @return None - """ - toolchain = self.__class__.__name__ - - # Create memap object - memap = MemapParser() - - # Parse and decode a map file - if memap.parse(abspath(map), toolchain) is False: - self.notify.info("Unknown toolchain for memory statistics %s" % toolchain) - return None - - # Store the memap instance for later use - self.memap_instance = memap - - # Note: memory statistics are not returned. - # Need call to generate_output later (depends on depth & output format) - - return None - - def _add_defines_from_region(self, region, linker_define=False, suffixes=['_ADDR', '_SIZE']): - for define in [(region.name.upper() + suffixes[0], region.start), - (region.name.upper() + suffixes[1], region.size)]: - define_string = "-D%s=0x%x" % define - self.cc.append(define_string) - self.cppc.append(define_string) - self.flags["common"].append(define_string) - if linker_define: - ld_string = ("%s" % define[0], "0x%x" % define[1]) - ld_string = self.make_ld_define(*ld_string) - self.ld.append(ld_string) - self.flags["ld"].append(ld_string) - - def _add_all_regions(self, region_list, active_region_name): - for region in region_list: - self._add_defines_from_region(region) - if region.active: - for define in [ - ("%s_START" % active_region_name, "0x%x" % region.start), - ("%s_SIZE" % active_region_name, "0x%x" % region.size) - ]: - define_string = self.make_ld_define(*define) - self.ld.append(define_string) - self.flags["ld"].append(define_string) - self.notify.info(" Region %s: size 0x%x, offset 0x%x" - % (region.name, region.size, region.start)) - - def add_regions(self): - """Add regions to the build profile, if there are any. - """ - if self.config.has_regions: - try: - regions = list(self.config.regions) - regions.sort(key=lambda x:x.start) - self.notify.info("Using ROM region%s %s in this build." % ( - "s" if len(regions) > 1 else "", - ", ".join(r.name for r in regions) - )) - self._add_all_regions(regions, "MBED_APP") - except ConfigException: - pass - - if self.config.has_ram_regions: - try: - regions = list(self.config.ram_regions) - self.notify.info("Using RAM region%s %s in this build." % ( - "s" if len(regions) > 1 else "", - ", ".join(r.name for r in regions) - )) - self._add_all_regions(regions, "MBED_RAM") - except ConfigException: - pass - - Region = namedtuple("Region", "name start size") - - try: - # Add all available ROM regions to build profile - if not getattr(self.target, "static_memory_defines", False): - raise ConfigException() - rom_available_regions = self.config.get_all_active_memories(ROM_ALL_MEMORIES) - for key, value in rom_available_regions.items(): - rom_start, rom_size = value - self._add_defines_from_region( - Region("MBED_" + key, rom_start, rom_size), - True, - suffixes=["_START", "_SIZE"] - ) - except ConfigException: - pass - try: - # Add all available RAM regions to build profile - if not getattr(self.target, "static_memory_defines", False): - raise ConfigException() - ram_available_regions = self.config.get_all_active_memories(RAM_ALL_MEMORIES) - for key, value in ram_available_regions.items(): - ram_start, ram_size = value - self._add_defines_from_region( - Region("MBED_" + key, ram_start, ram_size), - True, - suffixes=["_START", "_SIZE"] - ) - except ConfigException: - pass - - def add_linker_defines(self): - stack_param = "target.boot-stack-size" - params, _ = self.config_data - - if stack_param in params: - define_string = self.make_ld_define("MBED_BOOT_STACK_SIZE", int(params[stack_param].value, 0)) - self.ld.append(define_string) - self.flags["ld"].append(define_string) - - flags2params = {} - if self.target.is_PSA_non_secure_target: - flags2params = { - "MBED_ROM_START": "target.non-secure-rom-start", - "MBED_ROM_SIZE": "target.non-secure-rom-size", - "MBED_RAM_START": "target.non-secure-ram-start", - "MBED_RAM_SIZE": "target.non-secure-ram-size" - } - if self.target.is_PSA_secure_target: - flags2params = { - "MBED_ROM_START": "target.secure-rom-start", - "MBED_ROM_SIZE": "target.secure-rom-size", - "MBED_RAM_START": "target.secure-ram-start", - "MBED_RAM_SIZE": "target.secure-ram-size" - } - - for flag, param in flags2params.items(): - define_string = self.make_ld_define(flag, params[param].value) - self.ld.append(define_string) - self.flags["ld"].append(define_string) - - # Set the configuration data - def set_config_data(self, config_data): - self.config_data = config_data - # new configuration data can change labels, so clear the cache - self.labels = None - # pass info about softdevice presence to linker (see NRF52) - if "SOFTDEVICE_PRESENT" in config_data[1]: - define_string = self.make_ld_define("SOFTDEVICE_PRESENT", config_data[1]["SOFTDEVICE_PRESENT"].macro_value) - self.ld.append(define_string) - self.flags["ld"].append(define_string) - self.add_regions() - self.add_linker_defines() - - # Creates the configuration header if needed: - # - if there is no configuration data, "mbed_config.h" is not create (or deleted if it exists). - # - if there is configuration data and "mbed_config.h" does not exist, it is created. - # - if there is configuration data similar to the previous configuration data, - # "mbed_config.h" is left untouched. - # - if there is new configuration data, "mbed_config.h" is overriden. - # The function needs to be called exactly once for the lifetime of this toolchain instance. - # The "config_processed" variable (below) ensures this behaviour. - # The function returns the location of the configuration file, or None if there is no - # configuration data available (and thus no configuration file) - def get_config_header(self): - if self.config_processed: # this function was already called, return its result - return self.config_file - # The config file is located in the build directory - self.config_file = join(self.build_dir, self.MBED_CONFIG_FILE_NAME) - # If the file exists, read its current content in prev_data - if exists(self.config_file): - with open(self.config_file, "r") as f: - prev_data = f.read() - else: - prev_data = None - # Get the current configuration data - crt_data = self.config.config_to_header(self.config_data) if self.config_data else None - # "changed" indicates if a configuration change was detected - changed = False - if prev_data is not None: # a previous mbed_config.h exists - if crt_data is None: # no configuration data, so "mbed_config.h" needs to be removed - remove(self.config_file) - self.config_file = None # this means "config file not present" - changed = True - elif crt_data != prev_data: # different content of config file - with open(self.config_file, "w") as f: - f.write(crt_data) - changed = True - else: # a previous mbed_config.h does not exist - if crt_data is not None: # there's configuration data available - with open(self.config_file, "w") as f: - f.write(crt_data) - changed = True - else: - self.config_file = None # this means "config file not present" - # If there was a change in configuration, rebuild everything - self.build_all = changed - # Make sure that this function will only return the location of the configuration - # file for subsequent calls, without trying to manipulate its content in any way. - self.config_processed = True - return self.config_file - - def dump_build_profile(self): - """Dump the current build profile and macros into the `.profile` file - in the build directory""" - for key in ["cxx", "c", "asm", "ld"]: - to_dump = { - "flags": sorted(self.flags[key]), - "macros": sorted(self.macros), - "symbols": sorted(self.get_symbols(for_asm=(key == "asm"))), - } - if key in ["cxx", "c"]: - to_dump["symbols"].remove('MBED_BUILD_TIMESTAMP=%s' % self.timestamp) - to_dump["flags"].extend(sorted(self.flags['common'])) - where = join(self.build_dir, self.PROFILE_FILE_NAME + "-" + key) - self._overwrite_when_not_equal(where, json.dumps( - to_dump, sort_keys=True, indent=4)) - - @staticmethod - def _overwrite_when_not_equal(filename, content): - if not exists(filename) or content != open(filename).read(): - with open(filename, "w") as out: - out.write(content) - - @staticmethod - def generic_check_executable(tool_key, executable_name, levels_up, - nested_dir=None): - """ - Positional args: - tool_key: the key to index TOOLCHAIN_PATHS - executable_name: the toolchain's named executable (ex. armcc) - levels_up: each toolchain joins the toolchain_path, some - variable directories (bin, include), and the executable name, - so the TOOLCHAIN_PATH value must be appropriately distanced - - Keyword args: - nested_dir: the directory within TOOLCHAIN_PATHS where the executable - is found (ex: 'bin' for ARM\bin\armcc (necessary to check for path - that will be used by toolchain's compile) - - Returns True if the executable location specified by the user - exists and is valid OR the executable can be found on the PATH. - Returns False otherwise. - """ - # Search PATH if user did not specify a path or specified path doesn't - # exist. - if not TOOLCHAIN_PATHS[tool_key] or not exists(TOOLCHAIN_PATHS[tool_key]): - exe = find_executable(executable_name) - if not exe: - return False - for level in range(levels_up): - # move up the specified number of directories - exe = dirname(exe) - TOOLCHAIN_PATHS[tool_key] = exe - if nested_dir: - subdir = join(TOOLCHAIN_PATHS[tool_key], nested_dir, - executable_name) - else: - subdir = join(TOOLCHAIN_PATHS[tool_key],executable_name) - # User could have specified a path that exists but does not contain exe - return exists(subdir) or exists(subdir +'.exe') - - @abstractmethod - def check_executable(self): - """Returns True if the executable (armcc) location specified by the - user exists OR the executable can be found on the PATH. - Returns False otherwise.""" - raise NotImplemented - - @abstractmethod - def get_config_option(self, config_header): - """Generate the compiler option that forces the inclusion of the configuration - header file. - - Positional arguments: - config_header -- The configuration header that will be included within all source files - - Return value: - A list of the command line arguments that will force the inclusion the specified header - - Side effects: - None - """ - raise NotImplemented - - @abstractmethod - def get_compile_options(self, defines, includes, for_asm=False): - """Generate the compiler options from the defines and includes - - Positional arguments: - defines -- The preprocessor macros defined on the command line - includes -- The include file search paths - - Keyword arguments: - for_asm -- generate the assembler options instead of the compiler options - - Return value: - A list of the command line arguments that will force the inclusion the specified header - - Side effects: - None - """ - raise NotImplemented - - @abstractmethod - def assemble(self, source, object, includes): - """Generate the command line that assembles. - - Positional arguments: - source -- a file path that is the file to assemble - object -- a file path that is the destination object - includes -- a list of all directories where header files may be found - - Return value: - The complete command line, as a list, that would invoke the assembler - on the source file, include all the include paths, and generate - the specified object file. - - Side effects: - None - - Note: - This method should be decorated with @hook_tool. - """ - raise NotImplemented - - @abstractmethod - def compile_c(self, source, object, includes): - """Generate the command line that compiles a C source file. - - Positional arguments: - source -- the C source file to compile - object -- the destination object file - includes -- a list of all the directories where header files may be found - - Return value: - The complete command line, as a list, that would invoke the C compiler - on the source file, include all the include paths, and generate the - specified object file. - - Side effects: - None - - Note: - This method should be decorated with @hook_tool. - """ - raise NotImplemented - - @abstractmethod - def compile_cpp(self, source, object, includes): - """Generate the command line that compiles a C++ source file. - - Positional arguments: - source -- the C++ source file to compile - object -- the destination object file - includes -- a list of all the directories where header files may be found - - Return value: - The complete command line, as a list, that would invoke the C++ compiler - on the source file, include all the include paths, and generate the - specified object file. - - Side effects: - None - - Note: - This method should be decorated with @hook_tool. - """ - raise NotImplemented - - @abstractmethod - def link(self, output, objects, libraries, lib_dirs, mem_map): - """Run the linker to create an executable and memory map. - - Positional arguments: - output -- the file name to place the executable in - objects -- all of the object files to link - libraries -- all of the required libraries - lib_dirs -- where the required libraries are located - mem_map -- the location where the memory map file should be stored - - Return value: - None - - Side effect: - Runs the linker to produce the executable. - - Note: - This method should be decorated with @hook_tool. - """ - raise NotImplemented - - @abstractmethod - def archive(self, objects, lib_path): - """Run the command line that creates an archive. - - Positional arguhments: - objects -- a list of all the object files that should be archived - lib_path -- the file name of the resulting library file - - Return value: - None - - Side effect: - Runs the archiving tool to produce the library file. - - Note: - This method should be decorated with @hook_tool. - """ - raise NotImplemented - - @abstractmethod - def binary(self, resources, elf, bin): - """Run the command line that will Extract a simplified binary file. - - Positional arguments: - resources -- A resources object (Is not used in any of the toolchains) - elf -- the executable file that is to be converted - bin -- the file name of the to be created simplified binary file - - Return value: - None - - Side effect: - Runs the elf2bin tool to produce the simplified binary file. - - Note: - This method should be decorated with @hook_tool. - """ - raise NotImplemented - - @staticmethod - @abstractmethod - def name_mangle(name): - """Mangle a name based on the conventional name mangling of this toolchain - - Positional arguments: - name -- the name to mangle - - Return: - the mangled name as a string - """ - raise NotImplemented - - @staticmethod - @abstractmethod - def make_ld_define(name, value): - """Create an argument to the linker that would define a symbol - - Positional arguments: - name -- the symbol to define - value -- the value to give the symbol - - Return: - The linker flag as a string - """ - raise NotImplemented - - @staticmethod - @abstractmethod - def redirect_symbol(source, sync, build_dir): - """Redirect a symbol at link time to point at somewhere else - - Positional arguments: - source -- the symbol doing the pointing - sync -- the symbol being pointed to - build_dir -- the directory to put "response files" if needed by the toolchain - - Side Effects: - Possibly create a file in the build directory - - Return: - The linker flag to redirect the symbol, as a string - """ - raise NotImplemented - - # Return the list of macros geenrated by the build system - def get_config_macros(self): - return self.config.config_to_macros(self.config_data) if self.config_data else [] - - @abstractmethod - def version_check(self): - """Check the version of a compiler being used and raise a - NotSupportedException when it's incorrect. - """ - raise NotImplemented - - @property - def report(self): - to_ret = {} - to_ret['c_compiler'] = {'flags': copy(self.flags['c']), - 'symbols': self.get_symbols()} - to_ret['cxx_compiler'] = {'flags': copy(self.flags['cxx']), - 'symbols': self.get_symbols()} - to_ret['assembler'] = {'flags': copy(self.flags['asm']), - 'symbols': self.get_symbols(True)} - to_ret['linker'] = {'flags': copy(self.flags['ld'])} - to_ret.update(self.config.report) - return to_ret - -from tools.settings import ARM_PATH, ARMC6_PATH, GCC_ARM_PATH, IAR_PATH - -TOOLCHAIN_PATHS = { - 'ARM': ARM_PATH, - 'uARM': ARM_PATH, - 'ARMC6': ARMC6_PATH, - 'GCC_ARM': GCC_ARM_PATH, - 'IAR': IAR_PATH -} - -from tools.toolchains.arm import ARM_STD, ARM_MICRO, ARMC6 -from tools.toolchains.gcc import GCC_ARM -from tools.toolchains.iar import IAR +from . import mbed_toolchain, arm, gcc, iar TOOLCHAIN_CLASSES = { - u'ARM': ARM_STD, - u'uARM': ARM_MICRO, - u'ARMC6': ARMC6, - u'GCC_ARM': GCC_ARM, - u'IAR': IAR + u'ARM': arm.ARM_STD, + u'uARM': arm.ARM_MICRO, + u'ARMC6': arm.ARMC6, + u'GCC_ARM': gcc.GCC_ARM, + u'IAR': iar.IAR } TOOLCHAINS = set(TOOLCHAIN_CLASSES.keys()) + +# Top level re-exports +mbedToolchain = mbed_toolchain.mbedToolchain +TOOLCHAIN_PATHS = mbed_toolchain.TOOLCHAIN_PATHS diff --git a/tools/toolchains/arm.py b/tools/toolchains/arm.py index 82cfcdce66..62a132459b 100644 --- a/tools/toolchains/arm.py +++ b/tools/toolchains/arm.py @@ -15,32 +15,34 @@ See the License for the specific language governing permissions and limitations under the License. """ from __future__ import print_function, absolute_import -from builtins import str +from builtins import str # noqa: F401 import re from copy import copy -from os.path import join, dirname, splitext, basename, exists, relpath, isfile -from os import makedirs, write, curdir, remove +from os.path import join, dirname, splitext, basename, exists, isfile +from os import makedirs, write, remove from tempfile import mkstemp from shutil import rmtree from distutils.version import LooseVersion from tools.targets import CORE_ARCH -from tools.toolchains import mbedToolchain, TOOLCHAIN_PATHS -from tools.hooks import hook_tool +from tools.toolchains.mbed_toolchain import mbedToolchain, TOOLCHAIN_PATHS from tools.utils import mkdir, NotSupportedException, run_cmd + class ARM(mbedToolchain): LINKER_EXT = '.sct' LIBRARY_EXT = '.ar' STD_LIB_NAME = "%s.ar" - DIAGNOSTIC_PATTERN = re.compile('"(?P[^"]+)", line (?P\d+)( \(column (?P\d+)\)|): (?PWarning|Error|Fatal error): (?P.+)') - INDEX_PATTERN = re.compile('(?P\s*)\^') + DIAGNOSTIC_PATTERN = re.compile('"(?P[^"]+)", line (?P\d+)( \(column (?P\d+)\)|): (?PWarning|Error|Fatal error): (?P.+)') + INDEX_PATTERN = re.compile('(?P\s*)\^') DEP_PATTERN = re.compile('\S+:\s(?P.+)\n') SHEBANG = "#! armcc -E" - SUPPORTED_CORES = ["Cortex-M0", "Cortex-M0+", "Cortex-M3", "Cortex-M4", - "Cortex-M4F", "Cortex-M7", "Cortex-M7F", "Cortex-M7FD", "Cortex-A9"] + SUPPORTED_CORES = [ + "Cortex-M0", "Cortex-M0+", "Cortex-M3", "Cortex-M4", "Cortex-M4F", + "Cortex-M7", "Cortex-M7F", "Cortex-M7FD", "Cortex-A9" + ] ARMCC_RANGE = (LooseVersion("5.06"), LooseVersion("5.07")) ARMCC_VERSION_RE = re.compile(b"Component: ARM Compiler (\d+\.\d+)") @@ -82,7 +84,6 @@ class ARM(mbedToolchain): cpu = target.core ARM_BIN = join(TOOLCHAIN_PATHS['ARM'], "bin") - ARM_INC = join(TOOLCHAIN_PATHS['ARM'], "include") main_cc = join(ARM_BIN, "armcc") @@ -90,7 +91,10 @@ class ARM(mbedToolchain): self.asm = [main_cc] + self.flags['common'] + self.flags['asm'] self.cc = [main_cc] + self.flags['common'] + self.flags['c'] - self.cppc = [main_cc] + self.flags['common'] + self.flags['c'] + self.flags['cxx'] + self.cppc = ( + [main_cc] + self.flags['common'] + + self.flags['c'] + self.flags['cxx'] + ) self.ld = [join(ARM_BIN, "armlink")] + self.flags['ld'] @@ -104,9 +108,13 @@ class ARM(mbedToolchain): msg = None min_ver, max_ver = self.ARMCC_RANGE match = self.ARMCC_VERSION_RE.search(stdout.encode("utf-8")) - found_version = LooseVersion(match.group(1).decode("utf-8")) if match else None + if match: + found_version = LooseVersion(match.group(1).decode("utf-8")) + else: + found_version = None min_ver, max_ver = self.ARMCC_RANGE - if found_version and (found_version < min_ver or found_version >= max_ver): + if found_version and (found_version < min_ver + or found_version >= max_ver): msg = ("Compiler version mismatch: Have {}; " "expected version >= {} and < {}" .format(found_version, min_ver, max_ver)) @@ -135,8 +143,11 @@ class ARM(mbedToolchain): for line in open(dep_path).readlines(): match = ARM.DEP_PATTERN.match(line) if match is not None: - #we need to append chroot, because when the .d files are generated the compiler is chrooted - dependencies.append((self.CHROOT if self.CHROOT else '') + match.group('file')) + # we need to append chroot, because when the .d files are + # generated the compiler is chrooted + dependencies.append( + (self.CHROOT if self.CHROOT else '') + match.group('file') + ) return dependencies def parse_output(self, output): @@ -151,14 +162,18 @@ class ARM(mbedToolchain): 'severity': match.group('severity').lower(), 'file': match.group('file'), 'line': match.group('line'), - 'col': match.group('column') if match.group('column') else 0, 'message': match.group('message'), 'text': '', 'target_name': self.target.name, 'toolchain_name': self.name } + if match.group('column'): + msg['col'] = match.group('column') + else: + msg['col'] = 0 elif msg is not None: - # Determine the warning/error column by calculating the ^ position + # Determine the warning/error column by calculating the '^' + # position match = ARM.INDEX_PATTERN.match(line) if match is not None: msg['col'] = len(match.group('col')) @@ -192,7 +207,6 @@ class ARM(mbedToolchain): return opts - @hook_tool def assemble(self, source, object, includes): # Preprocess first, then assemble dir = join(dirname(object), '.temp') @@ -208,14 +222,9 @@ class ARM(mbedToolchain): # Build main assemble command cmd = self.asm + ["-o", object, tempfile] - # Call cmdline hook - cmd_pre = self.hook.get_cmdline_assembler(cmd_pre) - cmd = self.hook.get_cmdline_assembler(cmd) - # Return command array, don't execute return [cmd_pre, cmd] - @hook_tool def compile(self, cc, source, object, includes): # Build compile command cmd = cc + self.get_compile_options(self.get_symbols(), includes) @@ -224,9 +233,6 @@ class ARM(mbedToolchain): cmd.extend(["-o", object, source]) - # Call cmdline hook - cmd = self.hook.get_cmdline_compiler(cmd) - return [cmd] def compile_c(self, source, object, includes): @@ -254,7 +260,7 @@ class ARM(mbedToolchain): with open(scatter_file, "r") as input: lines = input.readlines() if (lines[0].startswith(self.SHEBANG) or - not lines[0].startswith("#!")): + not lines[0].startswith("#!")): return scatter_file else: new_scatter = join(self.build_dir, ".link_script.sct") @@ -269,7 +275,6 @@ class ARM(mbedToolchain): return new_scatter - @hook_tool def link(self, output, objects, libraries, lib_dirs, scatter_file): base, _ = splitext(output) map_file = base + ".map" @@ -282,8 +287,7 @@ class ARM(mbedToolchain): new_scatter = self.correct_scatter_shebang(scatter_file) args.extend(["--scatter", new_scatter]) - cmd_pre = self.ld + args - cmd = self.hook.get_cmdline_linker(cmd_pre) + cmd = self.ld + args if self.RESPONSE_FILES: cmd_linker = cmd[0] @@ -293,7 +297,6 @@ class ARM(mbedToolchain): self.notify.cc_verbose("Link: %s" % ' '.join(cmd)) self.default_cmd(cmd) - @hook_tool def archive(self, objects, lib_path): if self.RESPONSE_FILES: param = ['--via', self.get_arch_file(objects)] @@ -301,13 +304,12 @@ class ARM(mbedToolchain): param = objects self.default_cmd([self.ar, '-r', lib_path] + param) - @hook_tool def binary(self, resources, elf, bin): _, fmt = splitext(bin) - # On .hex format, combine multiple .hex files (for multiple load regions) into one + # On .hex format, combine multiple .hex files (for multiple load + # regions) into one bin_arg = {".bin": "--bin", ".hex": "--i32combined"}[fmt] cmd = [self.elf2bin, bin_arg, '-o', bin, elf] - cmd = self.hook.get_cmdline_binary(cmd) # remove target binary file/path if exists(bin): @@ -337,46 +339,95 @@ class ARM(mbedToolchain): class ARM_STD(ARM): + OFFICIALLY_SUPPORTED = True - def __init__(self, target, notify=None, macros=None, - build_profile=None, build_dir=None): - ARM.__init__(self, target, notify, macros, build_dir=build_dir, - build_profile=build_profile) + + def __init__( + self, + target, + notify=None, + macros=None, + build_profile=None, + build_dir=None + ): + ARM.__init__( + self, + target, + notify, + macros, + build_dir=build_dir, + build_profile=build_profile + ) if int(target.build_tools_metadata["version"]) > 0: - #check only for ARMC5 because ARM_STD means using ARMC5, and thus supported_toolchains must include ARMC5 + #check only for ARMC5 because ARM_STD means using ARMC5, and thus + # supported_toolchains must include ARMC5 if "ARMC5" not in target.supported_toolchains: - raise NotSupportedException("ARM compiler 5 support is required for ARM build") + raise NotSupportedException( + "ARM compiler 5 support is required for ARM build" + ) else: - if not set(("ARM", "uARM")).intersection(set(target.supported_toolchains)): - raise NotSupportedException("ARM/uARM compiler support is required for ARM build") + if not set(("ARM", "uARM")).intersection(set( + target.supported_toolchains + )): + raise NotSupportedException( + "ARM/uARM compiler support is required for ARM build" + ) class ARM_MICRO(ARM): - PATCHED_LIBRARY = False - OFFICIALLY_SUPPORTED = True - def __init__(self, target, notify=None, macros=None, - silent=False, extra_verbose=False, build_profile=None, - build_dir=None): - target.default_toolchain = "uARM" + PATCHED_LIBRARY = False + + OFFICIALLY_SUPPORTED = True + + def __init__( + self, + target, + notify=None, + macros=None, + silent=False, + extra_verbose=False, + build_profile=None, + build_dir=None + ): + target.default_toolchain = "uARM" if int(target.build_tools_metadata["version"]) > 0: - #At this point we already know that we want to use ARMC5+Microlib, so check for if they are supported - #For, AC6+Microlib we still use ARMC6 class - if not set(("ARMC5","uARM")).issubset(set(target.supported_toolchains)): - raise NotSupportedException("ARM/uARM compiler support is required for ARM build") + # At this point we already know that we want to use ARMC5+Microlib + # so check for if they are supported For, AC6+Microlib we still + # use ARMC6 class + if not set(("ARMC5","uARM")).issubset(set( + target.supported_toolchains + )): + raise NotSupportedException( + "ARM/uARM compiler support is required for ARM build" + ) else: - if not set(("ARM", "uARM")).intersection(set(target.supported_toolchains)): - raise NotSupportedException("ARM/uARM compiler support is required for ARM build") - ARM.__init__(self, target, notify, macros, build_dir=build_dir, - build_profile=build_profile) + if not set(("ARM", "uARM")).intersection(set( + target.supported_toolchains + )): + raise NotSupportedException( + "ARM/uARM compiler support is required for ARM build" + ) + ARM.__init__( + self, + target, + notify, + macros, + build_dir=build_dir, + build_profile=build_profile + ) + class ARMC6(ARM_STD): - OFFICIALLY_SUPPORTED = True + + OFFICIALLY_SUPPORTED = False SHEBANG = "#! armclang -E --target=arm-arm-none-eabi -x c" - SUPPORTED_CORES = ["Cortex-M0", "Cortex-M0+", "Cortex-M3", "Cortex-M4", - "Cortex-M4F", "Cortex-M7", "Cortex-M7F", "Cortex-M7FD", - "Cortex-M23", "Cortex-M23-NS", "Cortex-M33", "Cortex-M33F", - "Cortex-M33-NS", "Cortex-M33F-NS", "Cortex-M33FE-NS", "Cortex-M33FE", - "Cortex-A9"] + SUPPORTED_CORES = [ + "Cortex-M0", "Cortex-M0+", "Cortex-M3", "Cortex-M4", + "Cortex-M4F", "Cortex-M7", "Cortex-M7F", "Cortex-M7FD", + "Cortex-M23", "Cortex-M23-NS", "Cortex-M33", "Cortex-M33F", + "Cortex-M33-NS", "Cortex-M33F-NS", "Cortex-M33FE-NS", "Cortex-M33FE", + "Cortex-A9" + ] ARMCC_RANGE = (LooseVersion("6.10"), LooseVersion("7.0")) @staticmethod @@ -390,12 +441,20 @@ class ARMC6(ARM_STD): "this compiler does not support the core %s" % target.core) if int(target.build_tools_metadata["version"]) > 0: - if not set(("ARM", "ARMC6", "uARM")).intersection(set(target.supported_toolchains)): - raise NotSupportedException("ARM/ARMC6 compiler support is required for ARMC6 build") + if not set(("ARM", "ARMC6", "uARM")).intersection(set( + target.supported_toolchains + )): + raise NotSupportedException( + "ARM/ARMC6 compiler support is required for ARMC6 build" + ) else: - if not set(("ARM", "ARMC6")).intersection(set(target.supported_toolchains)): - raise NotSupportedException("ARM/ARMC6 compiler support is required for ARMC6 build") - + if not set(("ARM", "ARMC6")).intersection(set( + target.supported_toolchains + )): + raise NotSupportedException( + "ARM/ARMC6 compiler support is required for ARMC6 build" + ) + if getattr(target, "default_toolchain", "ARMC6") == "uARM": if "-DMBED_RTOS_SINGLE_THREAD" not in self.flags['common']: self.flags['common'].append("-DMBED_RTOS_SINGLE_THREAD") @@ -404,15 +463,16 @@ class ARMC6(ARM_STD): if "--library_type=microlib" not in self.flags['ld']: self.flags['ld'].append("--library_type=microlib") if "-Wl,--library_type=microlib" not in self.flags['c']: - self.flags['c'].append("-Wl,--library_type=microlib") + self.flags['c'].append("-Wl,--library_type=microlib") if "-Wl,--library_type=microlib" not in self.flags['cxx']: - self.flags['cxx'].append("-Wl,--library_type=microlib") + self.flags['cxx'].append("-Wl,--library_type=microlib") if "--library_type=microlib" not in self.flags['asm']: - self.flags['asm'].append("--library_type=microlib") + self.flags['asm'].append("--library_type=microlib") core = target.core if CORE_ARCH[target.core] == 8: - if (not target.core.endswith("-NS")) and kwargs.get('build_dir', False): + if ((not target.core.endswith("-NS")) and + kwargs.get('build_dir', False)): # Create Secure library build_dir = kwargs['build_dir'] secure_file = join(build_dir, "cmse_lib.o") @@ -479,8 +539,10 @@ class ARMC6(ARM_STD): self.flags['common'] + self.flags['c']) self.cppc = ([join(TOOLCHAIN_PATHS["ARMC6"], "armclang")] + self.flags['common'] + self.flags['cxx']) - self.asm = [join(TOOLCHAIN_PATHS["ARMC6"], "armasm")] + self.flags['asm'] - self.ld = [join(TOOLCHAIN_PATHS["ARMC6"], "armlink")] + self.flags['ld'] + self.asm = [join(TOOLCHAIN_PATHS["ARMC6"], "armasm")] + self.asm += self.flags['asm'] + self.ld = [join(TOOLCHAIN_PATHS["ARMC6"], "armlink")] + self.ld += self.flags['ld'] self.ar = join(TOOLCHAIN_PATHS["ARMC6"], "armar") self.elf2bin = join(TOOLCHAIN_PATHS["ARMC6"], "fromelf") @@ -514,22 +576,21 @@ class ARMC6(ARM_STD): if config_header: opts.extend(self.get_config_option(config_header)) if for_asm: - return ["--cpreproc", - "--cpreproc_opts=%s" % ",".join(self.flags['common'] + opts)] + return [ + "--cpreproc", + "--cpreproc_opts=%s" % ",".join(self.flags['common'] + opts) + ] return opts - @hook_tool def assemble(self, source, object, includes): cmd_pre = copy(self.asm) cmd_pre.extend(self.get_compile_options( self.get_symbols(True), includes, for_asm=True)) cmd_pre.extend(["-o", object, source]) - return [self.hook.get_cmdline_assembler(cmd_pre)] + return [cmd_pre] - @hook_tool def compile(self, cc, source, object, includes): cmd = copy(cc) cmd.extend(self.get_compile_options(self.get_symbols(), includes)) cmd.extend(["-o", object, source]) - cmd = self.hook.get_cmdline_compiler(cmd) return [cmd] diff --git a/tools/toolchains/gcc.py b/tools/toolchains/gcc.py index 56348bd589..22c0e3c72c 100644 --- a/tools/toolchains/gcc.py +++ b/tools/toolchains/gcc.py @@ -21,9 +21,9 @@ from distutils.spawn import find_executable from distutils.version import LooseVersion from tools.targets import CORE_ARCH -from tools.toolchains import mbedToolchain, TOOLCHAIN_PATHS -from tools.hooks import hook_tool -from tools.utils import run_cmd, NotSupportedException +from tools.toolchains.mbed_toolchain import mbedToolchain, TOOLCHAIN_PATHS +from tools.utils import run_cmd + class GCC(mbedToolchain): OFFICIALLY_SUPPORTED = True @@ -38,15 +38,21 @@ class GCC(mbedToolchain): def __init__(self, target, notify=None, macros=None, build_profile=None, build_dir=None): - mbedToolchain.__init__(self, target, notify, macros, - build_profile=build_profile, build_dir=build_dir) + mbedToolchain.__init__( + self, + target, + notify, + macros, + build_profile=build_profile, + build_dir=build_dir + ) - tool_path=TOOLCHAIN_PATHS['GCC_ARM'] + tool_path = TOOLCHAIN_PATHS['GCC_ARM'] # Add flags for current size setting default_lib = "std" if hasattr(target, "default_lib"): default_lib = target.default_lib - elif hasattr(target, "default_build"): # Legacy + elif hasattr(target, "default_build"): default_lib = target.default_build if default_lib == "small": @@ -110,8 +116,8 @@ class GCC(mbedToolchain): main_cc = join(tool_path, "arm-none-eabi-gcc") main_cppc = join(tool_path, "arm-none-eabi-g++") self.asm = [main_cc] + self.flags['asm'] + self.flags["common"] - self.cc = [main_cc] - self.cppc =[main_cppc] + self.cc = [main_cc] + self.cppc = [main_cppc] self.cc += self.flags['c'] + self.flags['common'] self.cppc += self.flags['cxx'] + self.flags['common'] @@ -130,9 +136,13 @@ class GCC(mbedToolchain): stdout, _, retcode = run_cmd([self.cc[0], "--version"], redirect=True) msg = None match = self.GCC_VERSION_RE.search(stdout.encode("utf-8")) - found_version = LooseVersion(match.group(0).decode('utf-8')) if match else None + if match: + found_version = LooseVersion(match.group(0).decode('utf-8')) + else: + found_version = None min_ver, max_ver = self.GCC_RANGE - if found_version and (found_version < min_ver or found_version >= max_ver): + if found_version and (found_version < min_ver + or found_version >= max_ver): msg = ("Compiler version mismatch: Have {}; " "expected version >= {} and < {}" .format(found_version, min_ver, max_ver)) @@ -195,18 +205,15 @@ class GCC(mbedToolchain): opts = opts + self.get_config_option(config_header) return opts - @hook_tool def assemble(self, source, object, includes): # Build assemble command - cmd = self.asm + self.get_compile_options(self.get_symbols(True), includes) + ["-o", object, source] - - # Call cmdline hook - cmd = self.hook.get_cmdline_assembler(cmd) + cmd = self.asm + self.get_compile_options( + self.get_symbols(True), includes + ) + ["-o", object, source] # Return command array, don't execute return [cmd] - @hook_tool def compile(self, cc, source, object, includes): # Build compile command cmd = cc + self.get_compile_options(self.get_symbols(), includes) @@ -215,8 +222,6 @@ class GCC(mbedToolchain): cmd.extend(["-o", object, source]) - # Call cmdline hook - cmd = self.hook.get_cmdline_compiler(cmd) if self.use_distcc: cmd = ["distcc"] + cmd @@ -228,7 +233,6 @@ class GCC(mbedToolchain): def compile_cpp(self, source, object, includes): return self.compile(self.cppc, source, object, includes) - @hook_tool def link(self, output, objects, libraries, lib_dirs, mem_map): libs = [] for l in libraries: @@ -239,15 +243,23 @@ class GCC(mbedToolchain): # Preprocess if mem_map: preproc_output = join(dirname(output), ".link_script.ld") - cmd = (self.preproc + [mem_map] + self.ld[1:] + - [ "-o", preproc_output]) + cmd = ( + self.preproc + [mem_map] + self.ld[1:] + ["-o", preproc_output] + ) self.notify.cc_verbose("Preproc: %s" % ' '.join(cmd)) self.default_cmd(cmd) mem_map = preproc_output # Build linker command map_file = splitext(output)[0] + ".map" - cmd = self.ld + ["-o", output, "-Wl,-Map=%s" % map_file] + objects + ["-Wl,--start-group"] + libs + ["-Wl,--end-group"] + cmd = ( + self.ld + + ["-o", output, "-Wl,-Map=%s" % map_file] + + objects + + ["-Wl,--start-group"] + + libs + + ["-Wl,--end-group"] + ) if mem_map: cmd.extend(['-T', mem_map]) @@ -256,9 +268,6 @@ class GCC(mbedToolchain): cmd.extend(['-L', L]) cmd.extend(libs) - # Call cmdline hook - cmd = self.hook.get_cmdline_linker(cmd) - if self.RESPONSE_FILES: # Split link command to linker executable + response file cmd_linker = cmd[0] @@ -269,7 +278,6 @@ class GCC(mbedToolchain): self.notify.cc_verbose("Link: %s" % ' '.join(cmd)) self.default_cmd(cmd) - @hook_tool def archive(self, objects, lib_path): if self.RESPONSE_FILES: param = ["@%s" % self.get_arch_file(objects)] @@ -279,16 +287,12 @@ class GCC(mbedToolchain): # Exec command self.default_cmd([self.ar, 'rcs', lib_path] + param) - @hook_tool def binary(self, resources, elf, bin): # Build binary command _, fmt = splitext(bin) bin_arg = {'.bin': 'binary', '.hex': 'ihex'}[fmt] cmd = [self.elf2bin, "-O", bin_arg, elf, bin] - # Call cmdline hook - cmd = self.hook.get_cmdline_binary(cmd) - # Exec command self.notify.cc_verbose("FromELF: %s" % ' '.join(cmd)) self.default_cmd(cmd) @@ -308,9 +312,12 @@ class GCC(mbedToolchain): @staticmethod def check_executable(): """Returns True if the executable (arm-none-eabi-gcc) location - specified by the user exists OR the executable can be found on the PATH. - Returns False otherwise.""" - if not TOOLCHAIN_PATHS['GCC_ARM'] or not exists(TOOLCHAIN_PATHS['GCC_ARM']): + specified by the user exists OR the executable can be found on the + PATH. Returns False otherwise.""" + if ( + not TOOLCHAIN_PATHS['GCC_ARM'] or + not exists(TOOLCHAIN_PATHS['GCC_ARM']) + ): if find_executable('arm-none-eabi-gcc'): TOOLCHAIN_PATHS['GCC_ARM'] = '' return True @@ -320,5 +327,6 @@ class GCC(mbedToolchain): exec_name = join(TOOLCHAIN_PATHS['GCC_ARM'], 'arm-none-eabi-gcc') return exists(exec_name) or exists(exec_name + '.exe') + class GCC_ARM(GCC): pass diff --git a/tools/toolchains/iar.py b/tools/toolchains/iar.py index 7751795aad..2a25825d0b 100644 --- a/tools/toolchains/iar.py +++ b/tools/toolchains/iar.py @@ -20,9 +20,9 @@ from os.path import join, splitext, exists from distutils.version import LooseVersion from tools.targets import CORE_ARCH -from tools.toolchains import mbedToolchain, TOOLCHAIN_PATHS -from tools.hooks import hook_tool -from tools.utils import run_cmd, NotSupportedException +from tools.toolchains.mbed_toolchain import mbedToolchain, TOOLCHAIN_PATHS +from tools.utils import run_cmd + class IAR(mbedToolchain): OFFICIALLY_SUPPORTED = True @@ -31,20 +31,29 @@ class IAR(mbedToolchain): STD_LIB_NAME = "%s.a" DIAGNOSTIC_PATTERN = re.compile('"(?P[^"]+)",(?P[\d]+)\s+(?PWarning|Error|Fatal error)(?P.+)') - INDEX_PATTERN = re.compile('(?P\s*)\^') + INDEX_PATTERN = re.compile('(?P\s*)\^') IAR_VERSION_RE = re.compile(b"IAR ANSI C/C\+\+ Compiler V(\d+\.\d+)") IAR_VERSION = LooseVersion("8.32") @staticmethod def check_executable(): """Returns True if the executable (arm-none-eabi-gcc) location - specified by the user exists OR the executable can be found on the PATH. - Returns False otherwise.""" - return mbedToolchain.generic_check_executable("IAR", 'iccarm', 2, "bin") + specified by the user exists OR the executable can be found on the + PATH. Returns False otherwise.""" + return mbedToolchain.generic_check_executable( + "IAR", 'iccarm', 2, "bin" + ) def __init__(self, target, notify=None, macros=None, build_profile=None, build_dir=None): - mbedToolchain.__init__(self, target, notify, macros, build_dir=build_dir, build_profile=build_profile) + mbedToolchain.__init__( + self, + target, + notify, + macros, + build_dir=build_dir, + build_profile=build_profile + ) core = target.core if CORE_ARCH[target.core] == 8: # Add linking time preprocessor macro DOMAIN_NS @@ -66,8 +75,8 @@ class IAR(mbedToolchain): "Cortex-M33F": "Cortex-M33.fp.no_dsp", "Cortex-M33FE": "Cortex-M33.fp"}.get(core, core) - # flags_cmd are used only by our scripts, the project files have them already defined, - # using this flags results in the errors (duplication) + # flags_cmd are used only by our scripts, the project files have them + # already defined, using this flags results in the errors (duplication) # asm accepts --cpu Core or --fpu FPU, not like c/c++ --cpu=Core asm_flags_cmd = ["--cpu", cpu] # custom c flags @@ -84,13 +93,22 @@ class IAR(mbedToolchain): IAR_BIN = join(TOOLCHAIN_PATHS['IAR'], "bin") main_cc = join(IAR_BIN, "iccarm") - self.asm = [join(IAR_BIN, "iasmarm")] + asm_flags_cmd + self.flags["asm"] - self.cc = [main_cc] + self.asm = [join(IAR_BIN, "iasmarm")] + self.asm += asm_flags_cmd + self.asm += self.flags["asm"] + + self.cc = [main_cc] + self.cc += self.flags["common"] + self.cc += c_flags_cmd + self.cc += self.flags["c"] + self.cppc = [main_cc] - self.cc += self.flags["common"] + c_flags_cmd + self.flags["c"] - self.cppc += self.flags["common"] + c_flags_cmd + cxx_flags_cmd + self.flags["cxx"] - - self.ld = [join(IAR_BIN, "ilinkarm")] + self.flags['ld'] + self.cppc += self.flags["common"] + self.cppc += c_flags_cmd + self.cppc += cxx_flags_cmd + self.cppc += self.flags["cxx"] + + self.ld = [join(IAR_BIN, "ilinkarm")] + self.flags['ld'] self.ar = join(IAR_BIN, "iarchive") self.elf2bin = join(IAR_BIN, "ielftool") @@ -114,10 +132,16 @@ class IAR(mbedToolchain): "severity": "Warning", }) + def _inner_parse_deps(self, dep_path): + for path in open(dep_path).readlines(): + if path and not path.isspace(): + if self.CHROOT: + yield self.CHROOT + path.strip() + else: + yield path.strip() def parse_dependencies(self, dep_path): - return [(self.CHROOT if self.CHROOT else '')+path.strip() for path in open(dep_path).readlines() - if (path and not path.isspace())] + return list(self._inner_parse_deps(dep_path)) def parse_output(self, output): msg = None @@ -138,7 +162,8 @@ class IAR(mbedToolchain): 'toolchain_name': self.name } elif msg is not None: - # Determine the warning/error column by calculating the ^ position + # Determine the warning/error column by calculating the '^' + # position match = IAR.INDEX_PATTERN.match(line) if match is not None: msg['col'] = len(match.group('col')) @@ -166,7 +191,7 @@ class IAR(mbedToolchain): opts = ['-D%s' % d for d in defines] if for_asm: config_macros = self.config.get_config_data_macros() - macros_cmd = ['"-D%s"' % d for d in config_macros if not '"' in d] + macros_cmd = ['"-D%s"' % d for d in config_macros if '"' not in d] if self.RESPONSE_FILES: via_file = self.make_option_file( macros_cmd, "asm_macros_{}.xcl") @@ -185,31 +210,21 @@ class IAR(mbedToolchain): return opts - @hook_tool def assemble(self, source, object, includes): # Build assemble command - cmd = self.asm + self.get_compile_options(self.get_symbols(True), includes, True) + ["-o", object, source] - - # Call cmdline hook - cmd = self.hook.get_cmdline_assembler(cmd) + cmd = self.asm + self.get_compile_options( + self.get_symbols(True), includes, True + ) + ["-o", object, source] # Return command array, don't execute return [cmd] - @hook_tool def compile(self, cc, source, object, includes): # Build compile command - cmd = cc + self.get_compile_options(self.get_symbols(), includes) - + cmd = cc + self.get_compile_options(self.get_symbols(), includes) cmd.extend(self.get_dep_option(object)) - cmd.extend(self.cc_extra(object)) - cmd.extend(["-o", object, source]) - - # Call cmdline hook - cmd = self.hook.get_cmdline_compiler(cmd) - return [cmd] def compile_c(self, source, object, includes): @@ -218,18 +233,16 @@ class IAR(mbedToolchain): def compile_cpp(self, source, object, includes): return self.compile(self.cppc, source, object, includes) - @hook_tool def link(self, output, objects, libraries, lib_dirs, mem_map): # Build linker command map_file = splitext(output)[0] + ".map" - cmd = self.ld + [ "-o", output, "--map=%s" % map_file] + objects + libraries + cmd = self.ld + ["-o", output, "--map=%s" % map_file] + cmd += objects + cmd += libraries if mem_map: cmd.extend(["--config", mem_map]) - # Call cmdline hook - cmd = self.hook.get_cmdline_linker(cmd) - if self.RESPONSE_FILES: # Split link command to linker executable + response file cmd_linker = cmd[0] @@ -240,7 +253,6 @@ class IAR(mbedToolchain): self.notify.cc_verbose("Link: %s" % ' '.join(cmd)) self.default_cmd(cmd) - @hook_tool def archive(self, objects, lib_path): if self.RESPONSE_FILES: param = ['-f', self.get_arch_file(objects)] @@ -252,16 +264,12 @@ class IAR(mbedToolchain): self.default_cmd([self.ar, lib_path] + param) - @hook_tool def binary(self, resources, elf, bin): _, fmt = splitext(bin) bin_arg = {".bin": "--bin", ".hex": "--ihex"}[fmt] # Build binary command cmd = [self.elf2bin, bin_arg, elf, bin] - # Call cmdline hook - cmd = self.hook.get_cmdline_binary(cmd) - # Exec command self.notify.cc_verbose("FromELF: %s" % ' '.join(cmd)) self.default_cmd(cmd) diff --git a/tools/toolchains/mbed_toolchain.py b/tools/toolchains/mbed_toolchain.py new file mode 100755 index 0000000000..6018648e2d --- /dev/null +++ b/tools/toolchains/mbed_toolchain.py @@ -0,0 +1,1304 @@ +""" +mbed SDK +SPDX-License-Identifier: Apache-2.0 +Copyright (c) 2011-2013 ARM Limited + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +from __future__ import print_function, division, absolute_import + +import re +import json +from os import stat, getcwd, getenv, rename, remove +from copy import copy +from time import time, sleep +from shutil import copyfile +from os.path import join, splitext, exists, relpath, dirname, split, abspath +from inspect import getmro +from copy import deepcopy +from collections import namedtuple +from abc import ABCMeta, abstractmethod +from distutils.spawn import find_executable +from multiprocessing import Pool, cpu_count +from hashlib import md5 + +from ..utils import ( + run_cmd, + mkdir, + ToolException, + NotSupportedException, + split_path, + compile_worker, + generate_update_filename, +) +from ..settings import MBED_ORG_USER, PRINT_COMPILER_OUTPUT_AS_LINK +from ..notifier.term import TerminalNotifier +from ..resources import FileType +from ..memap import MemapParser +from ..config import (ConfigException, RAM_ALL_MEMORIES, ROM_ALL_MEMORIES) +from ..regions import (UPDATE_WHITELIST, merge_region_list) +from ..settings import COMPARE_FIXED +from ..settings import ARM_PATH, ARMC6_PATH, GCC_ARM_PATH, IAR_PATH + + +TOOLCHAIN_PATHS = { + 'ARM': ARM_PATH, + 'uARM': ARM_PATH, + 'ARMC6': ARMC6_PATH, + 'GCC_ARM': GCC_ARM_PATH, + 'IAR': IAR_PATH +} + + +# Minimum job count in order to turn on parallel builds +CPU_COUNT_MIN = 1 +# Number of jobs to start for each CPU +CPU_COEF = 1 + +CORTEX_SYMBOLS = { + "Cortex-M0": ["__CORTEX_M0", "ARM_MATH_CM0", "__CMSIS_RTOS", + "__MBED_CMSIS_RTOS_CM"], + "Cortex-M0+": ["__CORTEX_M0PLUS", "ARM_MATH_CM0PLUS", "__CMSIS_RTOS", + "__MBED_CMSIS_RTOS_CM"], + "Cortex-M1": ["__CORTEX_M3", "ARM_MATH_CM1", "__CMSIS_RTOS", + "__MBED_CMSIS_RTOS_CM"], + "Cortex-M3": ["__CORTEX_M3", "ARM_MATH_CM3", "__CMSIS_RTOS", + "__MBED_CMSIS_RTOS_CM"], + "Cortex-M4": ["__CORTEX_M4", "ARM_MATH_CM4", "__CMSIS_RTOS", + "__MBED_CMSIS_RTOS_CM"], + "Cortex-M4F": ["__CORTEX_M4", "ARM_MATH_CM4", "__FPU_PRESENT=1", + "__CMSIS_RTOS", "__MBED_CMSIS_RTOS_CM"], + "Cortex-M7": ["__CORTEX_M7", "ARM_MATH_CM7", "__CMSIS_RTOS", + "__MBED_CMSIS_RTOS_CM"], + "Cortex-M7F": ["__CORTEX_M7", "ARM_MATH_CM7", "__FPU_PRESENT=1", + "__CMSIS_RTOS", "__MBED_CMSIS_RTOS_CM"], + "Cortex-M7FD": ["__CORTEX_M7", "ARM_MATH_CM7", "__FPU_PRESENT=1", + "__CMSIS_RTOS", "__MBED_CMSIS_RTOS_CM"], + "Cortex-A9": ["__CORTEX_A9", "ARM_MATH_CA9", "__FPU_PRESENT", + "__CMSIS_RTOS", "__EVAL", "__MBED_CMSIS_RTOS_CA9"], + "Cortex-M23-NS": ["__CORTEX_M23", "ARM_MATH_ARMV8MBL", "DOMAIN_NS=1", + "__CMSIS_RTOS", "__MBED_CMSIS_RTOS_CM"], + "Cortex-M23": ["__CORTEX_M23", "ARM_MATH_ARMV8MBL", "__CMSIS_RTOS", + "__MBED_CMSIS_RTOS_CM"], + "Cortex-M33-NS": ["__CORTEX_M33", "ARM_MATH_ARMV8MML", "DOMAIN_NS=1", + "__CMSIS_RTOS", "__MBED_CMSIS_RTOS_CM"], + "Cortex-M33": ["__CORTEX_M33", "ARM_MATH_ARMV8MML", "__CMSIS_RTOS", + "__MBED_CMSIS_RTOS_CM"], + "Cortex-M33F-NS": ["__CORTEX_M33", "ARM_MATH_ARMV8MML", "DOMAIN_NS=1", + "__FPU_PRESENT=1U", "__CMSIS_RTOS", + "__MBED_CMSIS_RTOS_CM"], + "Cortex-M33F": ["__CORTEX_M33", "ARM_MATH_ARMV8MML", + "__FPU_PRESENT=1U", "__CMSIS_RTOS", + "__MBED_CMSIS_RTOS_CM"], + "Cortex-M33FE-NS": ["__CORTEX_M33", "ARM_MATH_ARMV8MML", "DOMAIN_NS=1", + "__FPU_PRESENT=1U", "__CMSIS_RTOS", + "__MBED_CMSIS_RTOS_CM", "__DSP_PRESENT=1U"], + "Cortex-M33FE": ["__CORTEX_M33", "ARM_MATH_ARMV8MML", + "__FPU_PRESENT=1U", "__CMSIS_RTOS", + "__MBED_CMSIS_RTOS_CM", "__DSP_PRESENT=1U"], +} + + +class mbedToolchain: + OFFICIALLY_SUPPORTED = False + + # Verbose logging + VERBOSE = True + + # Compile C files as CPP + COMPILE_C_AS_CPP = False + + # Response files for compiling, includes, linking and archiving. + # Response files are files that contain arguments that would + # normally be passed on the command line. + RESPONSE_FILES = True + + MBED_CONFIG_FILE_NAME = "mbed_config.h" + + PROFILE_FILE_NAME = ".profile" + + __metaclass__ = ABCMeta + + profile_template = {'common': [], 'c': [], 'cxx': [], 'asm': [], 'ld': []} + + def __init__(self, target, notify=None, macros=None, build_profile=None, + build_dir=None): + self.target = target + self.name = self.__class__.__name__ + + # compile/assemble/link/binary hooks + self._post_build_hook = target.get_post_build_hook(self.name) + + # Toolchain flags + self.flags = deepcopy(build_profile or self.profile_template) + + # System libraries provided by the toolchain + self.sys_libs = [] + + # User-defined macros + self.macros = macros or [] + + # Macros generated from toolchain and target rules/features + self.asm_symbols = None + self.cxx_symbols = None + + # Labels generated from toolchain and target rules/features (used for + # selective build) + self.labels = None + + # This will hold the initialized config object + self.config = None + + self.config_data = None + + self.config_file = None + + # Call guard for "get_config_data" (see the comments of + # get_config_data for details) + self.config_processed = False + + # Non-incremental compile + self.build_all = False + + # Build output dir + if PRINT_COMPILER_OUTPUT_AS_LINK: + self.build_dir = abspath(build_dir) + else: + self.build_dir = build_dir + self.timestamp = getenv("MBED_BUILD_TIMESTAMP", time()) + + # Number of concurrent build jobs. 0 means host system cores + self.jobs = 0 + + if notify: + self.notify = notify + else: + self.notify = TerminalNotifier() + + # Stats cache is used to reduce the amount of IO requests to stat + # header files during dependency change. See need_update() + self.stat_cache = {} + + # Used by the mbed Online Build System to build in chrooted environment + self.CHROOT = None + + # post-init hook used by the online compiler TODO: remove this. + self.init() + + # TODO: This should not be needed, so remove it + # Used for post __init__() hooks + # THIS METHOD IS BEING OVERRIDDEN BY THE MBED ONLINE BUILD SYSTEM + # ANY CHANGE OF PARAMETERS OR RETURN VALUES WILL BREAK COMPATIBILITY + def init(self): + return True + + def get_output(self): + return self.notifier.get_output() + + def get_symbols(self, for_asm=False): + if for_asm: + if self.asm_symbols is None: + self.asm_symbols = [] + + # Cortex CPU symbols + if self.target.core in CORTEX_SYMBOLS: + self.asm_symbols.extend(CORTEX_SYMBOLS[self.target.core]) + + # Add target's symbols + self.asm_symbols += self.target.macros + # Add extra symbols passed via 'macros' parameter + self.asm_symbols += self.macros + return list(set(self.asm_symbols)) # Return only unique symbols + else: + if self.cxx_symbols is None: + # Target and Toolchain symbols + labels = self.get_labels() + self.cxx_symbols = ["TARGET_%s" % t for t in labels['TARGET']] + self.cxx_symbols.extend( + "TOOLCHAIN_%s" % t for t in labels['TOOLCHAIN'] + ) + + # Cortex CPU symbols + if self.target.core in CORTEX_SYMBOLS: + self.cxx_symbols.extend(CORTEX_SYMBOLS[self.target.core]) + + # Symbols defined by the on-line build.system + self.cxx_symbols.extend( + ['MBED_BUILD_TIMESTAMP=%s' % self.timestamp, + 'TARGET_LIKE_MBED', '__MBED__=1'] + ) + if MBED_ORG_USER: + self.cxx_symbols.append('MBED_USERNAME=' + MBED_ORG_USER) + + # Add target's name + self.cxx_symbols += ["TARGET_NAME=" + self.target.name] + # Add target's symbols + self.cxx_symbols += self.target.macros + # Add target's hardware + self.cxx_symbols += [ + "DEVICE_" + data + "=1" for data in self.target.device_has + ] + # Add target's features + self.cxx_symbols += [ + "FEATURE_" + data + "=1" for data in self.target.features + ] + # Add target's components + self.cxx_symbols += [ + "COMPONENT_" + data + "=1" + for data in self.target.components + ] + # Add extra symbols passed via 'macros' parameter + self.cxx_symbols += self.macros + + # Form factor variables + if hasattr(self.target, 'supported_form_factors'): + self.cxx_symbols.extend( + ["TARGET_FF_%s" % t for t in + self.target.supported_form_factors] + ) + + return list(set(self.cxx_symbols)) # Return only unique symbols + + # Extend the internal list of macros + def add_macros(self, new_macros): + self.macros.extend(new_macros) + + def get_labels(self): + if self.labels is None: + toolchain_labels = self._get_toolchain_labels() + self.labels = { + 'TARGET': self.target.labels, + 'FEATURE': self.target.features, + 'COMPONENT': self.target.components, + 'TOOLCHAIN': toolchain_labels + } + + # This is a policy decision and it should /really/ be in the config + # system ATM it's here for backward compatibility + if ((("-g" in self.flags['common'] or + "-g3" in self.flags['common']) and + "-O0" in self.flags['common']) or + ("-r" in self.flags['common'] and + "-On" in self.flags['common'])): + self.labels['TARGET'].append("DEBUG") + else: + self.labels['TARGET'].append("RELEASE") + return self.labels + + def _get_toolchain_labels(self): + toolchain_labels = [c.__name__ for c in getmro(self.__class__)] + toolchain_labels.remove('mbedToolchain') + toolchain_labels.remove('object') + return toolchain_labels + + # Determine whether a source file needs updating/compiling + def need_update(self, target, dependencies): + if self.build_all: + return True + + if not exists(target): + return True + + target_mod_time = stat(target).st_mtime + + for d in dependencies: + if not d or not exists(d): + return True + + if d not in self.stat_cache: + self.stat_cache[d] = stat(d).st_mtime + + if self.stat_cache[d] >= target_mod_time: + return True + + return False + + def copy_files(self, files_paths, trg_path, resources=None): + # Handle a single file + if not isinstance(files_paths, list): + files_paths = [files_paths] + + for dest, source in files_paths: + target = join(trg_path, dest) + if (target != source) and (self.need_update(target, [source])): + self.progress("copy", dest) + mkdir(dirname(target)) + copyfile(source, target) + + # TODO: the online build system places the target between + # the file name and file extension. Supporting multiple co-resident + # builds within a single directory would make this online override + # obsolite. + # THIS METHOD IS BEING OVERRIDDEN BY THE MBED ONLINE BUILD SYSTEM + # ANY CHANGE OF PARAMETERS OR RETURN VALUES WILL BREAK COMPATIBILITY + def relative_object_path(self, build_path, file_ref): + source_dir, name, _ = split_path(file_ref.name) + + obj_dir = relpath(join(build_path, source_dir)) + if obj_dir is not self.prev_dir: + self.prev_dir = obj_dir + mkdir(obj_dir) + return join(obj_dir, name + '.o') + + def make_option_file(self, options, naming=".options_{}.txt"): + """ Generate a via file for a pile of defines + ARM, GCC, IAR cross compatible + """ + to_write = " ".join(options).encode('utf-8') + new_md5 = md5(to_write).hexdigest() + via_file = join(self.build_dir, naming.format(new_md5)) + try: + with open(via_file, "r") as fd: + old_md5 = md5(fd.read().encode('utf-8')).hexdigest() + except IOError: + old_md5 = None + if old_md5 != new_md5: + with open(via_file, "wb") as fd: + fd.write(to_write) + return via_file + + def get_inc_file(self, includes): + """Generate a via file for all includes. + ARM, GCC, IAR cross compatible + """ + cmd_list = ( + "\"-I{}\"".format(c.replace("\\", "/")) for c in includes if c + ) + if self.CHROOT: + cmd_list = (c.replace(self.CHROOT, '') for c in cmd_list) + return self.make_option_file(list(cmd_list), naming=".includes_{}.txt") + + def get_link_file(self, cmd): + """Generate a via file for all objects when linking. + ARM, GCC, IAR cross compatible + """ + cmd_list = (c.replace("\\", "/") for c in cmd if c) + if self.CHROOT: + cmd_list = (c.replace(self.CHROOT, '') for c in cmd_list) + return self.make_option_file( + list(cmd_list), naming=".link_options.txt" + ) + + def get_arch_file(self, objects): + """ Generate a via file for all objects when archiving. + ARM, GCC, IAR cross compatible + """ + cmd_list = (c.replace("\\", "/") for c in objects if c) + return self.make_option_file(list(cmd_list), ".archive_files.txt") + + # THIS METHOD IS BEING CALLED BY THE MBED ONLINE BUILD SYSTEM + # ANY CHANGE OF PARAMETERS OR RETURN VALUES WILL BREAK COMPATIBILITY + def compile_sources(self, resources, inc_dirs=None): + # Web IDE progress bar for project build + files_to_compile = ( + resources.get_file_refs(FileType.ASM_SRC) + + resources.get_file_refs(FileType.C_SRC) + + resources.get_file_refs(FileType.CPP_SRC) + ) + self.to_be_compiled = len(files_to_compile) + self.compiled = 0 + + self.notify.cc_verbose("Macros: " + ' '.join([ + '-D%s' % s for s in self.get_symbols() + ])) + + inc_paths = resources.get_file_paths(FileType.INC_DIR) + if inc_dirs is not None: + if isinstance(inc_dirs, list): + inc_paths.extend(inc_dirs) + else: + inc_paths.append(inc_dirs) + # De-duplicate include paths + inc_paths = set(inc_paths) + # Sort include paths for consistency + inc_paths = sorted(set(inc_paths)) + # Unique id of all include paths + self.inc_md5 = md5(' '.join(inc_paths).encode('utf-8')).hexdigest() + + objects = [] + queue = [] + work_dir = getcwd() + self.prev_dir = None + + # Generate configuration header and update self.build_all + self.get_config_header() + self.dump_build_profile() + + # Sort compile queue for consistency + files_to_compile.sort() + for source in files_to_compile: + object = self.relative_object_path(self.build_dir, source) + + # Queue mode (multiprocessing) + commands = self.compile_command(source.path, object, inc_paths) + if commands is not None: + queue.append({ + 'source': source, + 'object': object, + 'commands': commands, + 'work_dir': work_dir, + 'chroot': self.CHROOT + }) + else: + self.compiled += 1 + objects.append(object) + + # Use queues/multiprocessing if cpu count is higher than setting + jobs = self.jobs if self.jobs else cpu_count() + if jobs > CPU_COUNT_MIN and len(queue) > jobs: + return self.compile_queue(queue, objects) + else: + return self.compile_seq(queue, objects) + + # Compile source files queue in sequential order + def compile_seq(self, queue, objects): + for item in queue: + result = compile_worker(item) + + self.compiled += 1 + self.progress("compile", item['source'].name, build_update=True) + for res in result['results']: + self.notify.cc_verbose( + "Compile: %s" % ' '.join(res['command']), + result['source'].name + ) + self.compile_output([ + res['code'], + res['output'], + res['command'] + ]) + objects.append(result['object']) + return objects + + # Compile source files queue in parallel by creating pool of worker threads + def compile_queue(self, queue, objects): + jobs_count = int(self.jobs if self.jobs else cpu_count() * CPU_COEF) + p = Pool(processes=jobs_count) + + results = [] + for i in range(len(queue)): + results.append(p.apply_async(compile_worker, [queue[i]])) + p.close() + + itr = 0 + while len(results): + itr += 1 + if itr > 180000: + p.terminate() + p.join() + raise ToolException("Compile did not finish in 5 minutes") + + sleep(0.01) + pending = 0 + for r in results: + if r.ready(): + try: + result = r.get() + results.remove(r) + + self.compiled += 1 + self.progress( + "compile", + result['source'].name, + build_update=True + ) + for res in result['results']: + self.notify.cc_verbose( + "Compile: %s" % ' '.join(res['command']), + result['source'].name + ) + self.compile_output([ + res['code'], + res['output'], + res['command'] + ]) + objects.append(result['object']) + except ToolException as err: + if p._taskqueue.queue: + p._taskqueue.queue.clear() + sleep(0.5) + p.terminate() + p.join() + raise ToolException(err) + else: + pending += 1 + if pending >= jobs_count: + break + + results = None + p.join() + + return objects + + # Determine the compile command based on type of source file + def compile_command(self, source, object, includes): + # Check dependencies + _, ext = splitext(source) + ext = ext.lower() + + source = abspath(source) if PRINT_COMPILER_OUTPUT_AS_LINK else source + + if ext == '.c' or ext == '.cpp' or ext == '.cc': + base, _ = splitext(object) + dep_path = base + '.d' + try: + if exists(dep_path): + deps = self.parse_dependencies(dep_path) + else: + deps = [] + except (IOError, IndexError): + deps = [] + config_file = ([self.config.app_config_location] + if self.config.app_config_location else []) + deps.extend(config_file) + if ext != '.c' or self.COMPILE_C_AS_CPP: + deps.append(join( + self.build_dir, self.PROFILE_FILE_NAME + "-cxx" + )) + else: + deps.append(join( + self.build_dir, self.PROFILE_FILE_NAME + "-c" + )) + if len(deps) == 0 or self.need_update(object, deps): + if ext != '.c' or self.COMPILE_C_AS_CPP: + return self.compile_cpp(source, object, includes) + else: + return self.compile_c(source, object, includes) + elif ext == '.s': + deps = [source] + deps.append(join(self.build_dir, self.PROFILE_FILE_NAME + "-asm")) + if self.need_update(object, deps): + return self.assemble(source, object, includes) + else: + return False + + return None + + def parse_dependencies(self, dep_path): + """Parse the dependency information generated by the compiler. + + Positional arguments: + dep_path -- the path to a file generated by a previous run of the + compiler + + Return value: + A list of all source files that the dependency file indicated were + dependencies + + Side effects: + None + + Note: A default implementation is provided for make-like file formats + """ + dependencies = [] + buff = open(dep_path).readlines() + if buff: + buff[0] = re.sub('^(.*?)\: ', '', buff[0]) + for line in buff: + filename = line.replace('\\\n', '').strip() + if filename: + filename = filename.replace('\\ ', '\a') + dependencies.extend(((self.CHROOT if self.CHROOT else '') + + f.replace('\a', ' ')) + for f in filename.split(" ")) + return list(filter(None, dependencies)) + + def is_not_supported_error(self, output): + return "#error directive: [NOT_SUPPORTED]" in output + + @abstractmethod + def parse_output(self, output): + """Take in compiler output and extract sinlge line warnings and errors from it. + + Positional arguments: + output -- a string of all the messages emitted by a run of the compiler + + Return value: + None + + Side effects: + call self.cc_info or self.notify with a description of the event + generated by the compiler + """ + raise NotImplemented + + def compile_output(self, output=[]): + rc = output[0] + stderr = output[1] + + # Parse output for Warnings and Errors + self.parse_output(stderr) + self.notify.debug("Return: %s" % rc) + for error_line in stderr.splitlines(): + self.notify.debug("Output: %s" % error_line) + + # Check return code + if rc != 0: + if self.is_not_supported_error(stderr): + raise NotSupportedException(stderr) + else: + raise ToolException(stderr) + + def build_library(self, objects, dir, name): + needed_update = False + lib = self.STD_LIB_NAME % name + fout = join(dir, lib) + if self.need_update(fout, objects): + self.notify.info("Library: %s" % lib) + self.archive(objects, fout) + needed_update = True + + return needed_update + + def _do_region_merge(self, name, binary, ext): + region_list = list(self.config.regions) + region_list = [r._replace(filename=binary) if r.active else r + for r in region_list] + res = "{}.{}".format(join(self.build_dir, name), ext) + merge_region_list(region_list, res, self.notify, self.config) + update_regions = [ + r for r in region_list if r.name in UPDATE_WHITELIST + ] + if update_regions: + update_res = join( + self.build_dir, + generate_update_filename(name, self.target) + ) + merge_region_list( + update_regions, + update_res, + self.notify, + self.config + ) + return res, update_res + else: + return res, None + + def link_program(self, r, tmp_path, name): + ext = getattr(self.target, "OUTPUT_EXT", "bin") + + if hasattr(self.target, 'OUTPUT_NAMING'): + self.notify.var("binary_naming", self.target.OUTPUT_NAMING) + if self.target.OUTPUT_NAMING == "8.3": + name = name[0:8] + ext = ext[0:3] + + # Create destination directory + head, tail = split(name) + new_path = join(tmp_path, head) + mkdir(new_path) + + # Absolute path of the final linked file + if self.config.has_regions: + elf = join(tmp_path, name + '_application.elf') + mapfile = join(tmp_path, name + '_application.map') + else: + elf = join(tmp_path, name + '.elf') + mapfile = join(tmp_path, name + '.map') + + objects = sorted(set(r.get_file_paths(FileType.OBJECT))) + config_file = ([self.config.app_config_location] + if self.config.app_config_location else []) + try: + linker_script = [ + path for _, path in r.get_file_refs(FileType.LD_SCRIPT) + if path.endswith(self.LINKER_EXT) + ][-1] + except IndexError: + raise NotSupportedException("No linker script found") + lib_dirs = r.get_file_paths(FileType.LIB_DIR) + libraries = [l for l in r.get_file_paths(FileType.LIB) + if l.endswith(self.LIBRARY_EXT)] + hex_files = r.get_file_paths(FileType.HEX) + dependencies = ( + objects + libraries + [linker_script] + config_file + hex_files + ) + dependencies.append(join( + self.build_dir, + self.PROFILE_FILE_NAME + "-ld" + )) + if self.need_update(elf, dependencies): + if not COMPARE_FIXED and exists(mapfile): + old_mapfile = "%s.old" % mapfile + if exists(old_mapfile): + remove(old_mapfile) + rename(mapfile, old_mapfile) + self.progress("link", name) + self.link(elf, objects, libraries, lib_dirs, linker_script) + + if self.config.has_regions: + filename = "{}_application.{}".format(name, ext) + else: + filename = "{}.{}".format(name, ext) + full_path = join(tmp_path, filename) + if ext != 'elf': + if full_path and self.need_update(full_path, [elf]): + self.progress("elf2bin", name) + self.binary(r, elf, full_path) + if self.config.has_regions: + full_path, updatable = self._do_region_merge( + name, full_path, ext + ) + else: + updatable = None + else: + updatable = None + + if self._post_build_hook: + self.progress("post-build", name) + self._post_build_hook(self, r, elf, full_path) + # Initialize memap and process map file. This doesn't generate output. + self.mem_stats(mapfile) + + self.notify.var("compile_succeded", True) + self.notify.var("binary", filename) + + return full_path, updatable + + # THIS METHOD IS BEING OVERRIDDEN BY THE MBED ONLINE BUILD SYSTEM + # ANY CHANGE OF PARAMETERS OR RETURN VALUES WILL BREAK COMPATIBILITY + def default_cmd(self, command): + stdout, stderr, rc = run_cmd( + command, work_dir=getcwd(), chroot=self.CHROOT + ) + self.notify.debug("Return: %s" % rc) + + for output_line in stdout.splitlines(): + self.notify.debug("Output: %s" % output_line) + for error_line in stderr.splitlines(): + self.notify.debug("Errors: %s" % error_line) + + if rc != 0: + for line in stderr.splitlines(): + self.notify.tool_error(line) + raise ToolException(stderr) + + def progress(self, action, file, build_update=False): + if build_update: + percent = 100. * float(self.compiled) / float(self.to_be_compiled) + else: + percent = None + self.notify.progress(action, file, percent) + + # THIS METHOD IS BEING OVERRIDDEN BY THE MBED ONLINE BUILD SYSTEM + # ANY CHANGE OF PARAMETERS OR RETURN VALUES WILL BREAK COMPATIBILITY + def mem_stats(self, map): + """! Creates parser object + @param map Path to linker map file to parse and decode + @return None + """ + toolchain = self.__class__.__name__ + + # Create memap object + memap = MemapParser() + + # Parse and decode a map file + if memap.parse(abspath(map), toolchain) is False: + self.notify.info( + "Unknown toolchain for memory statistics %s" % toolchain + ) + return None + + # Store the memap instance for later use + self.memap_instance = memap + + # Note: memory statistics are not returned. + # Need call to generate_output later (depends on depth & output format) + + return None + + def _add_defines_from_region( + self, + region, + linker_define=False, + suffixes=['_ADDR', '_SIZE'] + ): + for define in [(region.name.upper() + suffixes[0], region.start), + (region.name.upper() + suffixes[1], region.size)]: + define_string = "-D%s=0x%x" % define + self.cc.append(define_string) + self.cppc.append(define_string) + self.flags["common"].append(define_string) + if linker_define: + ld_string = ("%s" % define[0], "0x%x" % define[1]) + ld_string = self.make_ld_define(*ld_string) + self.ld.append(ld_string) + self.flags["ld"].append(ld_string) + + def _add_all_regions(self, region_list, active_region_name): + for region in region_list: + self._add_defines_from_region(region) + if region.active: + for define in [ + ("%s_START" % active_region_name, + "0x%x" % region.start), + ("%s_SIZE" % active_region_name, "0x%x" % region.size) + ]: + define_string = self.make_ld_define(*define) + self.ld.append(define_string) + self.flags["ld"].append(define_string) + self.notify.info(" Region %s: size 0x%x, offset 0x%x" + % (region.name, region.size, region.start)) + + def add_regions(self): + """Add regions to the build profile, if there are any. + """ + if self.config.has_regions: + try: + regions = list(self.config.regions) + regions.sort(key=lambda x: x.start) + self.notify.info("Using ROM region%s %s in this build." % ( + "s" if len(regions) > 1 else "", + ", ".join(r.name for r in regions) + )) + self._add_all_regions(regions, "MBED_APP") + except ConfigException: + pass + + if self.config.has_ram_regions: + try: + regions = list(self.config.ram_regions) + self.notify.info("Using RAM region%s %s in this build." % ( + "s" if len(regions) > 1 else "", + ", ".join(r.name for r in regions) + )) + self._add_all_regions(regions, "MBED_RAM") + except ConfigException: + pass + + Region = namedtuple("Region", "name start size") + + try: + # Add all available ROM regions to build profile + if not getattr(self.target, "static_memory_defines", False): + raise ConfigException() + rom_available_regions = self.config.get_all_active_memories( + ROM_ALL_MEMORIES + ) + for key, value in rom_available_regions.items(): + rom_start, rom_size = value + self._add_defines_from_region( + Region("MBED_" + key, rom_start, rom_size), + True, + suffixes=["_START", "_SIZE"] + ) + except ConfigException: + pass + try: + # Add all available RAM regions to build profile + if not getattr(self.target, "static_memory_defines", False): + raise ConfigException() + ram_available_regions = self.config.get_all_active_memories( + RAM_ALL_MEMORIES + ) + for key, value in ram_available_regions.items(): + ram_start, ram_size = value + self._add_defines_from_region( + Region("MBED_" + key, ram_start, ram_size), + True, + suffixes=["_START", "_SIZE"] + ) + except ConfigException: + pass + + STACK_PARAM = "target.boot-stack-size" + + def add_linker_defines(self): + params, _ = self.config_data + + if self.STACK_PARAM in params: + define_string = self.make_ld_define( + "MBED_BOOT_STACK_SIZE", + int(params[self.STACK_PARAM].value, 0) + ) + self.ld.append(define_string) + self.flags["ld"].append(define_string) + + flags2params = {} + if self.target.is_PSA_non_secure_target: + flags2params = { + "MBED_ROM_START": "target.non-secure-rom-start", + "MBED_ROM_SIZE": "target.non-secure-rom-size", + "MBED_RAM_START": "target.non-secure-ram-start", + "MBED_RAM_SIZE": "target.non-secure-ram-size" + } + if self.target.is_PSA_secure_target: + flags2params = { + "MBED_ROM_START": "target.secure-rom-start", + "MBED_ROM_SIZE": "target.secure-rom-size", + "MBED_RAM_START": "target.secure-ram-start", + "MBED_RAM_SIZE": "target.secure-ram-size" + } + + for flag, param in flags2params.items(): + define_string = self.make_ld_define(flag, params[param].value) + self.ld.append(define_string) + self.flags["ld"].append(define_string) + + # Set the configuration data + def set_config_data(self, config_data): + self.config_data = config_data + # new configuration data can change labels, so clear the cache + self.labels = None + # pass info about softdevice presence to linker (see NRF52) + if "SOFTDEVICE_PRESENT" in config_data[1]: + define_string = self.make_ld_define( + "SOFTDEVICE_PRESENT", + config_data[1]["SOFTDEVICE_PRESENT"].macro_value + ) + self.ld.append(define_string) + self.flags["ld"].append(define_string) + self.add_regions() + self.add_linker_defines() + + def get_config_header(self): + """ Creates the configuration header as needed. + The config file is located in the build directory + + - if there is no configuration data, "mbed_config.h" will not exists. + - if there is configuration data and "mbed_config.h" does not exist, + it is created. + - if there is configuration data that is the same as the previous + configuration data, "mbed_config.h" is left untouched. + - if there is new configuration data, "mbed_config.h" is overriden. + The function needs to be called exactly once for the lifetime of this + toolchain instance. + The "config_processed" variable (below) ensures this behaviour. + The function returns the location of the configuration file, or None + when there is no configuration data and file available. + """ + if self.config_processed: + return self.config_file + self.config_file = join(self.build_dir, self.MBED_CONFIG_FILE_NAME) + + if exists(self.config_file): + with open(self.config_file, "r") as f: + prev_data = f.read() + else: + prev_data = None + if self.config_data: + crt_data = self.config.config_to_header(self.config_data) + else: + crt_data = None + + changed = False + if prev_data is not None: + if crt_data is None: + remove(self.config_file) + self.config_file = None + changed = True + elif crt_data != prev_data: + with open(self.config_file, "w") as f: + f.write(crt_data) + changed = True + else: + if crt_data is not None: + with open(self.config_file, "w") as f: + f.write(crt_data) + changed = True + else: + self.config_file = None + self.build_all = changed + self.config_processed = True + return self.config_file + + def dump_build_profile(self): + """Dump the current build profile and macros into the `.profile` file + in the build directory""" + for key in ["cxx", "c", "asm", "ld"]: + to_dump = { + "flags": sorted(self.flags[key]), + "macros": sorted(self.macros), + "symbols": sorted(self.get_symbols(for_asm=(key == "asm"))), + } + if key in ["cxx", "c"]: + to_dump["symbols"].remove( + 'MBED_BUILD_TIMESTAMP=%s' % self.timestamp + ) + to_dump["flags"].extend(sorted(self.flags['common'])) + where = join(self.build_dir, self.PROFILE_FILE_NAME + "-" + key) + self._overwrite_when_not_equal(where, json.dumps( + to_dump, sort_keys=True, indent=4)) + + @staticmethod + def _overwrite_when_not_equal(filename, content): + if not exists(filename) or content != open(filename).read(): + with open(filename, "w") as out: + out.write(content) + + @staticmethod + def generic_check_executable(tool_key, executable_name, levels_up, + nested_dir=None): + """ + Positional args: + tool_key: the key to index TOOLCHAIN_PATHS + executable_name: the toolchain's named executable (ex. armcc) + levels_up: each toolchain joins the toolchain_path, some + variable directories (bin, include), and the executable name, + so the TOOLCHAIN_PATH value must be appropriately distanced + + Keyword args: + nested_dir: the directory within TOOLCHAIN_PATHS where the executable + is found (ex: 'bin' for ARM\bin\armcc (necessary to check for path + that will be used by toolchain's compile) + + Returns True if the executable location specified by the user + exists and is valid OR the executable can be found on the PATH. + Returns False otherwise. + """ + if (not TOOLCHAIN_PATHS[tool_key] or + not exists(TOOLCHAIN_PATHS[tool_key])): + exe = find_executable(executable_name) + if not exe: + return False + for level in range(levels_up): + # move up the specified number of directories + exe = dirname(exe) + TOOLCHAIN_PATHS[tool_key] = exe + if nested_dir: + subdir = join(TOOLCHAIN_PATHS[tool_key], nested_dir, + executable_name) + else: + subdir = join(TOOLCHAIN_PATHS[tool_key], executable_name) + # User could have specified a path that exists but does not contain exe + return exists(subdir) or exists(subdir + '.exe') + + @abstractmethod + def check_executable(self): + """Returns True if the executable (armcc) location specified by the + user exists OR the executable can be found on the PATH. + Returns False otherwise.""" + raise NotImplemented + + @abstractmethod + def get_config_option(self, config_header): + """Generate the compiler option that forces the inclusion of the configuration + header file. + + Positional arguments: + config_header -- The configuration header that will be included within + all source files + + Return value: + A list of the command line arguments that will force the inclusion the + specified header + + Side effects: + None + """ + raise NotImplemented + + @abstractmethod + def get_compile_options(self, defines, includes, for_asm=False): + """Generate the compiler options from the defines and includes + + Positional arguments: + defines -- The preprocessor macros defined on the command line + includes -- The include file search paths + + Keyword arguments: + for_asm -- generate the assembler options instead of the compiler + options + + Return value: + A list of the command line arguments that will force the inclusion the + specified header + + Side effects: + None + """ + raise NotImplemented + + @abstractmethod + def assemble(self, source, object, includes): + """Generate the command line that assembles. + + Positional arguments: + source -- a file path that is the file to assemble + object -- a file path that is the destination object + includes -- a list of all directories where header files may be found + + Return value: + The complete command line, as a list, that would invoke the assembler + on the source file, include all the include paths, and generate + the specified object file. + + Side effects: + None + """ + raise NotImplemented + + @abstractmethod + def compile_c(self, source, object, includes): + """Generate the command line that compiles a C source file. + + Positional arguments: + source -- the C source file to compile + object -- the destination object file + includes -- a list of all the directories where header files may be + found + + Return value: + The complete command line, as a list, that would invoke the C compiler + on the source file, include all the include paths, and generate the + specified object file. + + Side effects: + None + """ + raise NotImplemented + + @abstractmethod + def compile_cpp(self, source, object, includes): + """Generate the command line that compiles a C++ source file. + + Positional arguments: + source -- the C++ source file to compile + object -- the destination object file + includes -- a list of all the directories where header files may be + found + + Return value: + The complete command line, as a list, that would invoke the C++ + compiler on the source file, include all the include paths, and + generate the specified object file. + + Side effects: + None + """ + raise NotImplemented + + @abstractmethod + def link(self, output, objects, libraries, lib_dirs, mem_map): + """Run the linker to create an executable and memory map. + + Positional arguments: + output -- the file name to place the executable in + objects -- all of the object files to link + libraries -- all of the required libraries + lib_dirs -- where the required libraries are located + mem_map -- the location where the memory map file should be stored + + Return value: + None + + Side effect: + Runs the linker to produce the executable. + """ + raise NotImplemented + + @abstractmethod + def archive(self, objects, lib_path): + """Run the command line that creates an archive. + + Positional arguhments: + objects -- a list of all the object files that should be archived + lib_path -- the file name of the resulting library file + + Return value: + None + + Side effect: + Runs the archiving tool to produce the library file. + """ + raise NotImplemented + + @abstractmethod + def binary(self, resources, elf, bin): + """Run the command line that will Extract a simplified binary file. + + Positional arguments: + resources -- A resources object (Is not used in any of the toolchains) + elf -- the executable file that is to be converted + bin -- the file name of the to be created simplified binary file + + Return value: + None + + Side effect: + Runs the elf2bin tool to produce the simplified binary file. + """ + raise NotImplemented + + @staticmethod + @abstractmethod + def name_mangle(name): + """Mangle a name based on the conventional name mangling of this toolchain + + Positional arguments: + name -- the name to mangle + + Return: + the mangled name as a string + """ + raise NotImplemented + + @staticmethod + @abstractmethod + def make_ld_define(name, value): + """Create an argument to the linker that would define a symbol + + Positional arguments: + name -- the symbol to define + value -- the value to give the symbol + + Return: + The linker flag as a string + """ + raise NotImplemented + + @staticmethod + @abstractmethod + def redirect_symbol(source, sync, build_dir): + """Redirect a symbol at link time to point at somewhere else + + Positional arguments: + source -- the symbol doing the pointing + sync -- the symbol being pointed to + build_dir -- the directory to put "response files" if needed by the + toolchain + + Side Effects: + Possibly create a file in the build directory + + Return: + The linker flag to redirect the symbol, as a string + """ + raise NotImplemented + + def get_config_macros(self): + """ Return the list of macros generated by the build system """ + if self.config_data: + return self.config.config_to_macros(self.config_data) + else: + return [] + + @abstractmethod + def version_check(self): + """Check the version of a compiler being used and raise a + NotSupportedException when it's incorrect. + """ + raise NotImplemented + + @property + def report(self): + to_ret = {} + to_ret['c_compiler'] = {'flags': copy(self.flags['c']), + 'symbols': self.get_symbols()} + to_ret['cxx_compiler'] = {'flags': copy(self.flags['cxx']), + 'symbols': self.get_symbols()} + to_ret['assembler'] = {'flags': copy(self.flags['asm']), + 'symbols': self.get_symbols(True)} + to_ret['linker'] = {'flags': copy(self.flags['ld'])} + to_ret.update(self.config.report) + return to_ret