Diff: tools/export/exporters.py
- Revision:
- 0:6c56fb4bc5f0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/export/exporters.py Fri Nov 04 20:27:58 2016 +0000
@@ -0,0 +1,219 @@
+"""Just a template for subclassing"""
+import os
+import sys
+import logging
+from os.path import join, dirname, relpath
+from itertools import groupby
+from jinja2 import FileSystemLoader
+from jinja2.environment import Environment
+
+from tools.targets import TARGET_MAP
+from project_generator.tools import tool
+from project_generator.tools_supported import ToolsSupported
+from project_generator.settings import ProjectSettings
+from project_generator_definitions.definitions import ProGenDef
+
+
+class OldLibrariesException(Exception):
+ """Exception that indicates an export can not complete due to an out of date
+ library version.
+ """
+ pass
+
+class FailedBuildException(Exception):
+ """Exception that indicates that a build failed"""
+ pass
+
+class TargetNotSupportedException(Exception):
+ """Indicates that an IDE does not support a particular MCU"""
+ pass
+
+class ExporterTargetsProperty(object):
+ """ Exporter descriptor for TARGETS
+ TARGETS as class attribute for backward compatibility
+ (allows: if in Exporter.TARGETS)
+ """
+ def __init__(self, func):
+ self.func = func
+ def __get__(self, inst, cls):
+ return self.func(cls)
+
+class Exporter(object):
+ """Exporter base class
+
+ This class is meant to be extended by individual exporters, and provides a
+ few helper methods for implementing an exporter with either jinja2 or
+ progen.
+ """
+ TEMPLATE_DIR = dirname(__file__)
+ DOT_IN_RELATIVE_PATH = False
+ NAME = None
+ TARGETS = None
+ TOOLCHAIN = None
+
+ def __init__(self, target, export_dir, project_name, toolchain,
+ extra_symbols=None, resources=None):
+ """Initialize an instance of class exporter
+ Positional arguments:
+ target - the target mcu/board for this project
+ export_dir - the directory of the exported project files
+ project_name - the name of the project
+ toolchain - an instance of class toolchain
+
+ Keyword arguments:
+ extra_symbols - a list of extra macros for the toolchain
+ resources - an instance of class Resources
+ """
+ self.export_dir = export_dir
+ self.target = target
+ self.project_name = project_name
+ self.toolchain = toolchain
+ jinja_loader = FileSystemLoader(os.path.dirname(os.path.abspath(__file__)))
+ self.jinja_environment = Environment(loader=jinja_loader)
+ self.resources = resources
+ self.generated_files = []
+ self.builder_files_dict = {}
+
+ def get_toolchain(self):
+ """A helper getter function that we should probably eliminate"""
+ return self.TOOLCHAIN
+
+ @property
+ def flags(self):
+ """Returns a dictionary of toolchain flags.
+ Keys of the dictionary are:
+ cxx_flags - c++ flags
+ c_flags - c flags
+ ld_flags - linker flags
+ asm_flags - assembler flags
+ common_flags - common options
+ """
+ config_header = self.toolchain.get_config_header()
+ flags = {key + "_flags": value for key, value
+ in self.toolchain.flags.iteritems()}
+ asm_defines = ["-D" + symbol for symbol in self.toolchain.get_symbols(True)]
+ c_defines = ["-D" + symbol for symbol in self.toolchain.get_symbols()]
+ flags['asm_flags'] += asm_defines
+ flags['c_flags'] += c_defines
+ flags['cxx_flags'] += c_defines
+ if config_header:
+ config_header = relpath(config_header,
+ self.resources.file_basepath[config_header])
+ flags['c_flags'] += self.toolchain.get_config_option(config_header)
+ flags['cxx_flags'] += self.toolchain.get_config_option(
+ config_header)
+ return flags
+
+ def get_source_paths(self):
+ """Returns a list of the directories where source files are contained"""
+ source_keys = ['s_sources', 'c_sources', 'cpp_sources', 'hex_files',
+ 'objects', 'libraries']
+ source_files = []
+ for key in source_keys:
+ source_files.extend(getattr(self.resources, key))
+ return list(set([os.path.dirname(src) for src in source_files]))
+
+ def progen_get_project_data(self):
+ """ Get ProGen project data """
+ # provide default data, some tools don't require any additional
+ # tool specific settings
+
+ def make_key(src):
+ """turn a source file into it's group name"""
+ key = os.path.basename(os.path.dirname(src))
+ if not key:
+ key = os.path.basename(os.path.normpath(self.export_dir))
+ return key
+
+ def grouped(sources):
+ """Group the source files by their encompassing directory"""
+ data = sorted(sources, key=make_key)
+ return {k: list(g) for k, g in groupby(data, make_key)}
+
+ if self.toolchain.get_config_header():
+ config_header = self.toolchain.get_config_header()
+ config_header = relpath(config_header,
+ self.resources.file_basepath[config_header])
+ else:
+ config_header = None
+
+ # we want to add this to our include dirs
+ config_dir = os.path.dirname(config_header) if config_header else []
+
+ project_data = tool.get_tool_template()
+
+ project_data['target'] = TARGET_MAP[self.target].progen['target']
+ project_data['source_paths'] = self.get_source_paths()
+ project_data['include_paths'] = self.resources.inc_dirs + [config_dir]
+ project_data['include_files'] = grouped(self.resources.headers)
+ project_data['source_files_s'] = grouped(self.resources.s_sources)
+ project_data['source_files_c'] = grouped(self.resources.c_sources)
+ project_data['source_files_cpp'] = grouped(self.resources.cpp_sources)
+ project_data['source_files_obj'] = grouped(self.resources.objects)
+ project_data['source_files_lib'] = grouped(self.resources.libraries)
+ project_data['output_dir']['path'] = self.export_dir
+ project_data['linker_file'] = self.resources.linker_script
+ project_data['macros'] = []
+ project_data['build_dir'] = 'build'
+ project_data['template'] = None
+ project_data['name'] = self.project_name
+ project_data['output_type'] = 'exe'
+ project_data['debugger'] = None
+ return project_data
+
+ def progen_gen_file(self, project_data):
+ """ Generate project using ProGen Project API
+ Positional arguments:
+ tool_name - the tool for which to generate project files
+ project_data - a dict whose base key, values are specified in
+ progen_get_project_data, the items will have been
+ modified by Exporter subclasses
+
+ Keyword arguments:
+ progen_build - A boolean that determines if the tool will build the
+ project
+ """
+ if not self.check_supported(self.NAME):
+ raise TargetNotSupportedException("Target not supported")
+ settings = ProjectSettings()
+ exporter = ToolsSupported().get_tool(self.NAME)
+ self.builder_files_dict = {self.NAME:exporter(project_data, settings).export_project()}
+ for middle in self.builder_files_dict.values():
+ for field, thing in middle.iteritems():
+ if field == "files":
+ for filename in thing.values():
+ self.generated_files.append(filename)
+
+ def progen_build(self):
+ """Build a project that was already generated by progen"""
+ print("Project {} exported, building for {}...".format(
+ self.project_name, self.NAME))
+ sys.stdout.flush()
+ builder = ToolsSupported().get_tool(self.NAME)
+ result = builder(self.builder_files_dict[self.NAME], ProjectSettings()).build_project()
+ if result == -1:
+ raise FailedBuildException("Build Failed")
+
+ def check_supported(self, ide):
+ """Indicated if this combination of IDE and MCU is supported"""
+ if self.target not in self.TARGETS or \
+ self.TOOLCHAIN not in TARGET_MAP[self.target].supported_toolchains:
+ return False
+ if not ProGenDef(ide).is_supported(
+ TARGET_MAP[self.target].progen['target']):
+ return False
+ return True
+
+ def gen_file(self, template_file, data, target_file):
+ """Generates a project file from a template using jinja"""
+ jinja_loader = FileSystemLoader(
+ os.path.dirname(os.path.abspath(__file__)))
+ jinja_environment = Environment(loader=jinja_loader)
+
+ template = jinja_environment.get_template(template_file)
+ target_text = template.render(data)
+
+ target_path = join(self.export_dir, target_file)
+ logging.debug("Generating: %s", target_path)
+ open(target_path, "w").write(target_text)
+ self.generated_files += [target_path]