Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers exporters.py Source File

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