mbed-os/tools/export/nb/__init__.py

384 lines
14 KiB
Python

from __future__ import print_function, absolute_import
from builtins import str
import os
import copy
import shutil
from os.path import relpath, join, exists, dirname, basename
from os import makedirs, remove
from json import load
from tools.export.exporters import Exporter, apply_supported_whitelist
from tools.targets import TARGET_MAP
from tools.utils import NotSupportedException
from tools.build_api import prepare_toolchain
POST_BINARY_WHITELIST = set([
"TEENSY3_1Code.binary_hook",
"MCU_NRF51Code.binary_hook",
"LPCTargetCode.lpc_patch",
"LPC4088Code.binary_hook"
])
class GNUARMNetbeans(Exporter):
NAME = 'GNU ARM Netbeans'
TOOLCHAIN = 'GCC_ARM'
@classmethod
def is_target_supported(cls, target_name):
target = TARGET_MAP[target_name]
return apply_supported_whitelist(
cls.TOOLCHAIN, POST_BINARY_WHITELIST, target)
@staticmethod
def prepare_sys_lib(libname):
return "-l" + libname
def toolchain_flags(self, toolchain):
"""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
The difference from the above is that it takes a parameter.
"""
# Note: use the config options from the currently selected toolchain.
config_header = self.toolchain.get_config_header()
flags = {key + "_flags": copy.deepcopy(value) for key, value
in toolchain.flags.items()}
if config_header:
config_header = relpath(config_header,
self.resources.file_basepath[config_header])
header_options = self.toolchain.get_config_option(config_header)
flags['c_flags'] += header_options
flags['cxx_flags'] += header_options
return flags
@staticmethod
def get_defines_and_remove_from_flags(flags_in, str_key):
defines = []
flags_temp = copy.deepcopy(flags_in)
for f in flags_temp[str_key]:
f = f.strip()
if f.startswith('-D'):
defines.append(f[2:])
flags_in[str_key].remove(f)
return defines
@staticmethod
def get_includes_and_remove_from_flags(flags_in, str_key):
includes = []
flags_temp = copy.deepcopy(flags_in)
next_is_include = False
for f in flags_temp[str_key]:
f = f.strip()
if next_is_include:
includes.append(f)
flags_in[str_key].remove(f)
next_is_include = False
continue
if f == "-include":
flags_in[str_key].remove(f)
next_is_include = True
return includes
@staticmethod
def get_c_std_and_remove_from_flag(flags_in, str_key):
comp_std = ''
c_std = {
'c90': 'c90', 'c89': 'c90', 'gnu90': 'gnu90', 'gnu89': 'gnu90',
'c99': 'c99', 'c9x': 'c99', 'gnu99': 'gnu99', 'gnu9x': 'gnu98',
'c11': 'c11', 'c1x': 'c11', 'gnu11': 'gnu11', 'gnu1x': 'gnu11'
}
cpp_std = {
'c++98': 'cpp98', 'c++03': 'cpp98',
'gnu++98': 'gnucpp98', 'gnu++03': 'gnucpp98',
'c++0x': 'cpp0x', 'gnu++0x': 'gnucpp0x',
'c++11': 'cpp11', 'gnu++11': 'gnucpp11',
'c++1y': 'cpp1y', 'gnu++1y': 'gnucpp1y',
'c++14': 'cpp14', 'gnu++14': 'gnucpp14',
'c++1z': 'cpp1z', 'gnu++1z': 'gnucpp1z',
}
flags_temp = copy.deepcopy(flags_in)
for f in flags_temp[str_key]:
f = f.strip()
if f.startswith('-std='):
comp_std = f[len('-std='):]
flags_in[str_key].remove(f)
elif f.startswith('-'):
std = f[len('-'):]
if std in c_std or std in cpp_std:
comp_std = std
flags_in[str_key].remove(f)
return comp_std
def validate_resources(self):
if not self.resources.linker_script:
raise NotSupportedException("No linker script found.")
def create_jinja_ctx(self):
self.options = {}
flags = {}
self.validate_resources()
# Convert all Backslashes to Forward Slashes
self.resources.win_to_unix()
self.ld_script = self.filter_dot(
self.resources.linker_script)
# Read in all profiles, we'll extract compiler options.
profiles = self.get_all_profiles()
profile_ids = [s.lower() for s in profiles]
profile_ids.sort()
for prof_id in profile_ids:
# There are 4 categories of options, a category common too
# all tools and a specific category for each of the tools.
opts = {}
opts['defines'] = {}
opts['common'] = {}
opts['as'] = {}
opts['c'] = {}
opts['cpp'] = {}
opts['ld'] = {}
opts['id'] = prof_id
opts['name'] = opts['id'].capitalize()
profile = profiles[prof_id]
# A small hack, do not bother with src_path again,
# pass an empty string to avoid crashing.
src_paths = ['']
target_name = self.toolchain.target.name
toolchain = prepare_toolchain(
src_paths, "", target_name, self.TOOLCHAIN, build_profile=[profile])
flags = self.toolchain_flags(toolchain)
opts['defines'] = self.get_defines_and_remove_from_flags(flags, 'common_flags')
opts['forced_includes'] = self.get_includes_and_remove_from_flags(flags, 'common_flags')
opts['common'] = flags['common_flags']
opts['as'] = flags['asm_flags']
opts['c'] = flags['c_flags']
opts['cpp'] = flags['cxx_flags']
opts['ld'] = flags['ld_flags']
self.options[prof_id] = opts
sources = [] # list of strings
forced_includes = self.get_includes_and_remove_from_flags(flags, 'c_flags')
forced_includes += self.get_includes_and_remove_from_flags(flags, 'cxx_flags')
# Remove Duplicates
forced_includes = list(set(forced_includes))
c_std = self.get_c_std_and_remove_from_flag(flags, 'c_flags')
cpp_std = self.get_c_std_and_remove_from_flag(flags, 'cxx_flags')
# Make one list of all resources
for r_type in ['c_sources', 's_sources', 'cpp_sources']:
sources.extend(getattr(self.resources, r_type))
# Remove all leading './'
c_sources = [self.filter_dot(field) for field in self.resources.c_sources]
cpp_sources = [self.filter_dot(field) for field in self.resources.cpp_sources]
s_sources = [self.filter_dot(field) for field in self.resources.s_sources]
headers = [self.filter_dot(field) for field in self.resources.headers]
sources = [self.filter_dot(field) for field in sources]
include_paths = [self.filter_dot(field) for field in self.resources.inc_dirs]
sys_libs = [self.prepare_sys_lib(lib) for lib
in self.toolchain.sys_libs]
preproc = " ".join([basename(self.toolchain.preproc[0])] +
self.toolchain.preproc[1:] +
self.toolchain.ld[1:])
if 'nbproject' in include_paths:
include_paths.remove('nbproject')
jinja_ctx = {
'name': self.project_name,
'target': self.toolchain.target.name,
'elf_location': join('BUILD', self.project_name) + '.elf',
'c_symbols': self.toolchain.get_symbols(),
'asm_symbols': self.toolchain.get_symbols(True),
'c_flags': flags['c_flags'],
'cxx_flags': flags['cxx_flags'],
'ld_flags': self.flags['ld_flags'],
'asm_flags': self.flags['asm_flags'],
'common_flags': self.flags['common_flags'],
'include_paths': include_paths,
'forced_includes': forced_includes,
'c_sources': c_sources,
'cpp_sources': cpp_sources,
's_sources': s_sources,
'headers': headers,
'headers_folder': self.get_netbeans_file_list(sorted(headers)),
'sources_folder': self.get_netbeans_file_list(sorted(sources)),
'options': self.options,
'c_std': self.get_netbeans_c_std(c_std),
'cpp_std': self.get_netbeans_cpp_std(cpp_std),
'linker_script': self.ld_script,
'linker_libs': sys_libs,
'pp_cmd': preproc,
'cc_cmd': self.toolchain.cc[0],
'cppc_cmd': self.toolchain.cppc[0],
'asm_cmd': self.toolchain.asm[0],
'ld_cmd': self.toolchain.ld[0],
'elf2bin_cmd': self.toolchain.elf2bin
}
return jinja_ctx
def generate(self):
"""Generate Makefile, configurations.xml & project.xml Netbeans project file
"""
jinja_ctx = self.create_jinja_ctx()
if not exists(join(self.export_dir, 'nbproject')):
makedirs(join(self.export_dir, 'nbproject'))
self.gen_file('nb/configurations.tmpl', jinja_ctx, 'nbproject/configurations.xml')
self.gen_file('nb/project.tmpl', jinja_ctx, 'nbproject/project.xml')
self.gen_file_nonoverwrite('nb/mbedignore.tmpl', jinja_ctx,
'.mbedignore')
self.gen_file('nb/Makefile.tmpl', jinja_ctx, 'Makefile')
print('Done. Import the \'{0}\' project in Netbeans.'.format(self.project_name))
@staticmethod
def clean(_):
shutil.rmtree("nbproject")
remove("Makefile")
# -------------------------------------------------------------------------
@staticmethod
def filter_dot(str_in):
"""
Remove the './' prefix, if present.
This function assumes that resources.win_to_unix()
replaced all windows backslashes with slashes.
"""
if str_in is None:
return None
if str_in[:2] == './':
return str_in[2:]
return str_in
# -------------------------------------------------------------------------
@staticmethod
def get_all_profiles():
tools_path = dirname(dirname(dirname(__file__)))
file_names = [join(tools_path, "profiles", fn) for fn in os.listdir(
join(tools_path, "profiles")) if fn.endswith(".json")]
profiles = {}
for fn in file_names:
content = load(open(fn))
profile_name = basename(fn).replace(".json", "")
profiles[profile_name] = content
return profiles
@staticmethod
def get_netbeans_file_list(file_list):
cur_dir = ''
prev_dir = ''
output = []
folder_count = 1
dir_depth = 0
for item in file_list:
cur_dir = os.path.dirname(item)
dir_temp = os.path.normpath(cur_dir)
prev_dir_temp = os.path.normpath(prev_dir)
dir_list = dir_temp.split(os.sep)
prev_dir_list = prev_dir_temp.split(os.sep)
dir_depth = len(dir_list)
# Current File is in Directory: Compare the given dir with previous Dir
if cur_dir and prev_dir != cur_dir:
# evaluate all matched items (from current and previous list)
matched = []
# Compare the Element in Previous Dir with the Elements in Current Dir
# and add the equal Elements to the match-List
for elem_prev_dir, elem_cur_dir in zip(prev_dir_list, dir_list):
if elem_prev_dir == elem_cur_dir:
matched.append(elem_cur_dir)
# calculate difference between matched and length
diff = dir_depth - len(matched)
# if previous dir was not root
if prev_dir != '':
# if the elements count is not equal we calculate the difference
if len(dir_list) != len(prev_dir_list):
dir_depth_prev = len(prev_dir_list)
delta = dir_depth_prev - len(matched)
for i in range(dir_depth_prev - delta, dir_depth_prev):
output.append('</logicalFolder>')
# if the elements count is equal, we subtract the matched length from the total length
else:
for i in range(len(matched), len(dir_list)):
output.append('</logicalFolder>')
for i in range(dir_depth - diff, dir_depth):
output.append('<logicalFolder name="f' + str(folder_count) + '" displayName="' + str(
dir_list[i]) + '" projectFiles="true">')
folder_count += 1
# Current File is in root
else:
# Close Tag if we are in root and the previous dir wasn't
if cur_dir == '' and prev_dir != '':
for i in range(0, len(prev_dir_list)):
output.append('</logicalFolder>')
# Save the Current Dir
prev_dir = cur_dir
output.append('<itemPath>' + str(item) + '</itemPath>')
if cur_dir != '':
# close all open tags
output.append('</logicalFolder>' * dir_depth)
return output
@staticmethod
def get_netbeans_c_std(c_std):
c_std_netbeans = 0
if '89' in c_std:
c_std_netbeans = 2
elif '99' in c_std:
c_std_netbeans = 3
elif '11' in c_std:
c_std_netbeans = 10
return c_std_netbeans
@staticmethod
def get_netbeans_cpp_std(cpp_std):
cpp_std_netbeans = 0
if '98' in cpp_std:
cpp_std_netbeans = 4
elif '11' in cpp_std:
cpp_std_netbeans = 8
elif '14' in cpp_std:
cpp_std_netbeans = 11
return cpp_std_netbeans