Anders Blomdell / mbed-sdk-tools
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 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