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.
exporters.py
00001 """Just a template for subclassing""" 00002 import os 00003 from abc import abstractmethod, ABCMeta 00004 import logging 00005 from os.path import join, dirname, relpath, basename, realpath, normpath, exists 00006 from itertools import groupby 00007 from jinja2 import FileSystemLoader, StrictUndefined 00008 from jinja2.environment import Environment 00009 import copy 00010 00011 from tools.targets import TARGET_MAP 00012 from tools.utils import mkdir 00013 from tools.resources import FileType 00014 00015 00016 class TargetNotSupportedException (Exception): 00017 """Indicates that an IDE does not support a particular MCU""" 00018 pass 00019 00020 class ExporterTargetsProperty (object): 00021 """ Exporter descriptor for TARGETS 00022 TARGETS as class attribute for backward compatibility 00023 (allows: if in Exporter.TARGETS) 00024 """ 00025 def __init__(self, func): 00026 self.func = func 00027 def __get__(self, inst, cls): 00028 return self.func(cls) 00029 00030 def deprecated_exporter(CLS): 00031 old_init = CLS.__init__ 00032 old_name = CLS.NAME 00033 def __init__(*args, **kwargs): 00034 print("==================== DEPRECATION NOTICE ====================") 00035 print("The exporter %s is no longer maintained, and deprecated." % old_name) 00036 print("%s will be removed from mbed OS for the mbed OS 5.6 release." % old_name) 00037 old_init(*args, **kwargs) 00038 CLS.__init__ = __init__ 00039 CLS.NAME = "%s (DEPRECATED)" % old_name 00040 return CLS 00041 00042 class Exporter (object): 00043 """Exporter base class 00044 00045 This class is meant to be extended by individual exporters, and provides a 00046 few helper methods for implementing an exporter with either jinja2 or 00047 progen. 00048 """ 00049 __metaclass__ = ABCMeta 00050 TEMPLATE_DIR = dirname(__file__) 00051 DOT_IN_RELATIVE_PATH = False 00052 NAME = None 00053 TARGETS = set() 00054 TOOLCHAIN = None 00055 CLEAN_FILES = ("GettingStarted.html",) 00056 00057 00058 def __init__ (self, target, export_dir, project_name, toolchain, 00059 extra_symbols=None, resources=None): 00060 """Initialize an instance of class exporter 00061 Positional arguments: 00062 target - the target mcu/board for this project 00063 export_dir - the directory of the exported project files 00064 project_name - the name of the project 00065 toolchain - an instance of class toolchain 00066 00067 Keyword arguments: 00068 extra_symbols - a list of extra macros for the toolchain 00069 resources - an instance of class Resources 00070 """ 00071 self.export_dir = export_dir 00072 self.target = target 00073 self.project_name = project_name 00074 self.toolchain = toolchain 00075 jinja_loader = FileSystemLoader(os.path.dirname(os.path.abspath(__file__))) 00076 self.jinja_environment = Environment(loader=jinja_loader) 00077 self.resources = resources 00078 self.generated_files = [] 00079 self.static_files = ( 00080 join(self.TEMPLATE_DIR , "GettingStarted.html"), 00081 join(self.TEMPLATE_DIR , ".mbed"), 00082 ) 00083 self.builder_files_dict = {} 00084 self.add_config () 00085 00086 def get_toolchain (self): 00087 """A helper getter function that we should probably eliminate""" 00088 return self.TOOLCHAIN 00089 00090 def add_config (self): 00091 """Add the containing directory of mbed_config.h to include dirs""" 00092 pass 00093 00094 @property 00095 def flags (self): 00096 """Returns a dictionary of toolchain flags. 00097 Keys of the dictionary are: 00098 cxx_flags - c++ flags 00099 c_flags - c flags 00100 ld_flags - linker flags 00101 asm_flags - assembler flags 00102 common_flags - common options 00103 """ 00104 flags = self.toolchain_flags (self.toolchain ) 00105 asm_defines = self.toolchain .get_compile_options( 00106 self.toolchain .get_symbols(for_asm=True), 00107 filter(None, self.resources .inc_dirs), 00108 for_asm=True) 00109 c_defines = ["-D" + symbol for symbol in self.toolchain .get_symbols()] 00110 flags['asm_flags'] += asm_defines 00111 flags['c_flags'] += c_defines 00112 flags['cxx_flags'] += c_defines 00113 config_header = self.config_header_ref 00114 if config_header: 00115 config_option = self.toolchain .get_config_option( 00116 config_header.name) 00117 flags['c_flags'] += config_option 00118 flags['cxx_flags'] += config_option 00119 return flags 00120 00121 @property 00122 def libraries(self): 00123 return [l for l in self.resources .get_file_names(FileType.LIB) 00124 if l.endswith(self.toolchain .LIBRARY_EXT)] 00125 00126 def toolchain_flags (self, toolchain): 00127 """Returns a dictionary of toolchain flags. 00128 Keys of the dictionary are: 00129 cxx_flags - c++ flags 00130 c_flags - c flags 00131 ld_flags - linker flags 00132 asm_flags - assembler flags 00133 common_flags - common options 00134 00135 The difference from the above is that it takes a parameter. 00136 """ 00137 flags = {key + "_flags": copy.deepcopy(value) for key, value 00138 in toolchain.flags.items()} 00139 config_header = self.config_header_ref 00140 if config_header: 00141 header_options = self.toolchain .get_config_option( 00142 config_header.name) 00143 flags['c_flags'] += header_options 00144 flags['cxx_flags'] += header_options 00145 return flags 00146 00147 @property 00148 def config_header_ref(self): 00149 config_header = self.toolchain .get_config_header() 00150 if config_header: 00151 def is_config_header(f): 00152 return f.path == config_header 00153 return list(filter( 00154 is_config_header, self.resources .get_file_refs(FileType.HEADER) 00155 ))[0] 00156 else: 00157 return None 00158 00159 def get_source_paths (self): 00160 """Returns a list of the directories where source files are contained""" 00161 source_keys = ['s_sources', 'c_sources', 'cpp_sources', 'hex_files', 00162 'objects', 'libraries'] 00163 source_files = [] 00164 for key in source_keys: 00165 source_files.extend(getattr(self.resources , key)) 00166 return list(set([os.path.dirname(src) for src in source_files])) 00167 00168 def gen_file_dest (self, target_file): 00169 """Generate the project file location in an exported project""" 00170 return join(self.export_dir , target_file) 00171 00172 def gen_file (self, template_file, data, target_file, **kwargs): 00173 """Generates a project file from a template using jinja""" 00174 target_text = self._gen_file_inner (template_file, data, target_file, **kwargs) 00175 target_path = self.gen_file_dest (target_file) 00176 mkdir(dirname(target_path)) 00177 logging.debug("Generating: %s", target_path) 00178 open(target_path, "w").write(target_text) 00179 self.generated_files += [target_path] 00180 00181 def gen_file_nonoverwrite (self, template_file, data, target_file, **kwargs): 00182 """Generates a project file from a template using jinja""" 00183 target_text = self._gen_file_inner (template_file, data, target_file, **kwargs) 00184 target_path = self.gen_file_dest (target_file) 00185 if exists(target_path): 00186 with open(target_path) as fdin: 00187 old_text = fdin.read() 00188 if target_text not in old_text: 00189 with open(target_path, "a") as fdout: 00190 fdout.write(target_text) 00191 else: 00192 logging.debug("Generating: %s", target_path) 00193 open(target_path, "w").write(target_text) 00194 self.generated_files += [target_path] 00195 00196 def _gen_file_inner(self, template_file, data, target_file, **kwargs): 00197 """Generates a project file from a template using jinja""" 00198 jinja_loader = FileSystemLoader( 00199 os.path.dirname(os.path.abspath(__file__))) 00200 jinja_environment = Environment(loader=jinja_loader, 00201 undefined=StrictUndefined, **kwargs) 00202 00203 template = jinja_environment.get_template(template_file) 00204 target_text = template.render(data) 00205 return target_text 00206 00207 target_path = join(self.export_dir , target_file) 00208 logging.debug("Generating: %s", target_path) 00209 open(target_path, "w").write(target_text) 00210 self.generated_files += [target_path] 00211 00212 def make_key (self, src): 00213 """From a source file, extract group name 00214 Positional Arguments: 00215 src - the src's location 00216 """ 00217 path_list = os.path.normpath(src).split(os.sep) 00218 assert len(path_list) >= 1 00219 if len(path_list) == 1: 00220 key = self.project_name 00221 else: 00222 key = path_list[0] 00223 return key 00224 00225 def group_project_files (self, sources): 00226 """Group the source files by their encompassing directory 00227 Positional Arguments: 00228 sources - array of source locations 00229 00230 Returns a dictionary of {group name: list of source locations} 00231 """ 00232 data = sorted(sources, key=self.make_key ) 00233 return {k: list(g) for k,g in groupby(data, self.make_key )} 00234 00235 @staticmethod 00236 def build (project_name, log_name='build_log.txt', cleanup=True): 00237 """Invoke exporters build command within a subprocess. 00238 This method is assumed to be executed at the same level as exporter 00239 project files and project source code. 00240 See uvision/__init__.py, iar/__init__.py, and makefile/__init__.py for 00241 example implemenation. 00242 00243 Positional Arguments: 00244 project_name - the name of the project to build; often required by 00245 exporter's build command. 00246 00247 Keyword Args: 00248 log_name - name of the build log to create. Written and printed out, 00249 deleted if cleanup = True 00250 cleanup - a boolean dictating whether exported project files and 00251 build log are removed after build 00252 00253 Returns -1 on failure and 0 on success 00254 """ 00255 raise NotImplementedError("Implement in derived Exporter class.") 00256 00257 @staticmethod 00258 def clean (project_name): 00259 """Clean a previously exported project 00260 This method is assumed to be executed at the same level as exporter 00261 project files and project source code. 00262 See uvision/__init__.py, iar/__init__.py, and makefile/__init__.py for 00263 example implemenation. 00264 00265 Positional Arguments: 00266 project_name - the name of the project to build; often required by 00267 exporter's build command. 00268 00269 Returns nothing. May raise exceptions 00270 """ 00271 raise NotImplementedError("Implement in derived Exporter class.") 00272 00273 @abstractmethod 00274 def generate (self): 00275 """Generate an IDE/tool specific project file""" 00276 raise NotImplementedError("Implement a generate function in Exporter child class") 00277 00278 @classmethod 00279 def is_target_supported (cls, target_name): 00280 """Query support for a particular target 00281 00282 NOTE: override this method if your exporter does not provide a static list of targets 00283 00284 Positional Arguments: 00285 target_name - the name of the target. 00286 """ 00287 target = TARGET_MAP[target_name] 00288 return bool(set(target.resolution_order_names).intersection(set(cls.TARGETS))) \ 00289 and cls.TOOLCHAIN in target.supported_toolchains 00290 00291 00292 @classmethod 00293 def all_supported_targets(cls): 00294 return [t for t in TARGET_MAP.keys() if cls.is_target_supported(t)] 00295 00296 @staticmethod 00297 def filter_dot (str): 00298 """ 00299 Remove the './' or '.\\' prefix, if present. 00300 """ 00301 if str == None: 00302 return None 00303 if str[:2] == './': 00304 return str[2:] 00305 if str[:2] == '.\\': 00306 return str[2:] 00307 return str 00308 00309 00310 00311 def apply_supported_whitelist (compiler, whitelist, target): 00312 """Generate a list of supported targets for a given compiler and post-binary hook 00313 white-list.""" 00314 if compiler not in target.supported_toolchains: 00315 return False 00316 if not hasattr(target, "post_binary_hook"): 00317 return True 00318 if target.post_binary_hook['function'] in whitelist: 00319 return True 00320 else: 00321 return False
Generated on Tue Jul 12 2022 17:12:47 by
