Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Diff: toolchains/__init__.py
- Revision:
- 24:25bff2709c20
- Parent:
- 22:9e85236d8716
- Child:
- 27:5461402c33f8
diff -r fbae331171fa -r 25bff2709c20 toolchains/__init__.py --- a/toolchains/__init__.py Sat Jul 16 22:51:17 2016 +0100 +++ b/toolchains/__init__.py Mon Aug 01 09:10:17 2016 +0100 @@ -17,7 +17,7 @@ import re import sys -from os import stat, walk, getcwd, sep +from os import stat, walk, getcwd, sep, remove from copy import copy from time import time, sleep from types import ListType @@ -26,6 +26,7 @@ from inspect import getmro from copy import deepcopy from tools.config import Config +from abc import ABCMeta, abstractmethod from multiprocessing import Pool, cpu_count from tools.utils import run_cmd, mkdir, rel_path, ToolException, NotSupportedException, split_path, compile_worker @@ -188,8 +189,14 @@ class mbedToolchain: + # 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 = { @@ -207,6 +214,8 @@ MBED_CONFIG_FILE_NAME="mbed_config.h" + __metaclass__ = ABCMeta + def __init__(self, target, options=None, notify=None, macros=None, silent=False, extra_verbose=False): self.target = target self.name = self.__class__.__name__ @@ -226,9 +235,18 @@ # 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 @@ -242,8 +260,6 @@ # Number of concurrent build jobs. 0 means auto (based on host system cores) self.jobs = 0 - self.CHROOT = None - # Ignore patterns from .mbedignore files self.ignore_patterns = [] @@ -285,12 +301,20 @@ # uVisor spepcific rules if 'UVISOR' in self.target.features and 'UVISOR_SUPPORTED' in self.target.extra_labels: self.target.core = re.sub(r"F$", '', self.target.core) - + + # 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() - # This allows post __init__() hooks. Do not use + # 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 @@ -340,6 +364,8 @@ elif event['type'] == 'progress': self.print_notify(event) # standard handle + # THIS METHOD IS BEING OVERRIDDEN BY THE MBED ONLINE BUILD SYSTEM + # ANY CHANGE OF PARAMETERS OR RETURN VALUES WILL BREAK COMPATIBILITY def notify(self, event): """ Little closure for notify functions """ @@ -392,6 +418,8 @@ } return self.labels + + # Determine whether a source file needs updating/compiling def need_update(self, target, dependencies): if self.build_all: return True @@ -601,6 +629,8 @@ 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, base_dir, source): source_dir, name, _ = split_path(source) @@ -610,6 +640,8 @@ mkdir(obj_dir) return join(obj_dir, name + '.o') + # Generate response file for all includes. + # ARM, GCC, IAR cross compatible def get_inc_file(self, includes): include_file = join(self.build_dir, ".includes_%s.txt" % self.inc_md5) if not exists(include_file): @@ -625,6 +657,8 @@ f.write(string) return include_file + # Generate response file for all objects when linking. + # ARM, GCC, IAR cross compatible def get_link_file(self, cmd): link_file = join(self.build_dir, ".link_files.txt") with open(link_file, "wb") as f: @@ -639,6 +673,8 @@ f.write(string) return link_file + # Generate response file for all objects when archiving. + # ARM, GCC, IAR cross compatible def get_arch_file(self, objects): archive_file = join(self.build_dir, ".archive_files.txt") with open(archive_file, "wb") as f: @@ -649,6 +685,8 @@ f.write(string) return archive_file + # 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, build_path, inc_dirs=None): # Web IDE progress bar for project build files_to_compile = resources.s_sources + resources.c_sources + resources.cpp_sources @@ -674,6 +712,9 @@ work_dir = getcwd() self.prev_dir = None + # Generate configuration header (this will update self.build_all if needed) + self.get_config_header() + # Sort compile queue for consistency files_to_compile.sort() for source in files_to_compile: @@ -699,6 +740,7 @@ 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) @@ -715,6 +757,7 @@ 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) @@ -764,6 +807,7 @@ return objects + # Determine the compile command based on type of source file def compile_command(self, source, object, includes): # Check dependencies _, ext = splitext(source) @@ -787,9 +831,39 @@ return None + @abstractmethod + 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 + """ + raise NotImplemented + 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] @@ -858,6 +932,8 @@ return bin, 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.debug("Return: %s"% _rc) @@ -876,6 +952,8 @@ def info(self, message): self.notify({'type': 'info', 'message': message}) + # THIS METHOD IS BEING OVERRIDDEN BY THE MBED ONLINE BUILD SYSTEM + # ANY CHANGE OF PARAMETERS OR RETURN VALUES WILL BREAK COMPATIBILITY def debug(self, message): if self.VERBOSE: if type(message) is ListType: @@ -883,11 +961,15 @@ message = "[DEBUG] " + message self.notify({'type': 'debug', 'message': message}) + # THIS METHOD IS BEING OVERRIDDEN BY THE MBED ONLINE BUILD SYSTEM + # ANY CHANGE OF PARAMETERS OR RETURN VALUES WILL BREAK COMPATIBILITY def cc_info(self, info=None): if info is not None: info['type'] = 'cc' self.notify(info) + # THIS METHOD IS BEING OVERRIDDEN BY THE MBED ONLINE BUILD SYSTEM + # ANY CHANGE OF PARAMETERS OR RETURN VALUES WILL BREAK COMPATIBILITY def cc_verbose(self, message, file=""): self.debug(message) @@ -903,6 +985,8 @@ def var(self, key, value): self.notify({'type': 'var', 'key': key, 'val': value}) + # 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 @@ -939,25 +1023,201 @@ def set_config_data(self, config_data): self.config_data = config_data - # Return the location of the config header. This function will create the config - # header first if needed. The header will be written in a file called "mbed_conf.h" - # located in the project's build directory. - # If config headers are not used (self.config_header_content is None), the function - # returns None + # 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_data is None: - return None - config_file = join(self.build_dir, self.MBED_CONFIG_FILE_NAME) - if not exists(config_file): - with open(config_file, "wt") as f: - f.write(Config.config_to_header(self.config_data)) - return config_file + 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, "rt") as f: + prev_data = f.read() + else: + prev_data = None + # Get the current configuration data + crt_data = 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, "wt") 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, "wt") 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 + + @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 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 # Return the list of macros geenrated by the build system def get_config_macros(self): return Config.config_to_macros(self.config_data) if self.config_data else [] - from tools.settings import ARM_PATH from tools.settings import GCC_ARM_PATH, GCC_CR_PATH from tools.settings import IAR_PATH