mbed-os/workspace_tools/toolchains/__init__.py

397 lines
14 KiB
Python
Raw Normal View History

from os import stat, walk, remove
from os.path import join, splitext, exists, relpath, dirname, basename, split
from shutil import copyfile
from copy import copy
from types import ListType
from inspect import getmro
from workspace_tools.utils import run_cmd, mkdir, rel_path, ToolException, split_path
from workspace_tools.patch import patch
def print_notify(event):
# Default command line notification
if event['type'] in ['info', 'debug']:
print event['message']
elif event['type'] == 'cc':
event['severity'] = event['severity'].title()
event['file'] = basename(event['file'])
print '[%(severity)s] %(file)s@%(line)s: %(message)s' % event
elif event['type'] == 'progress':
print '%s: %s' % (event['action'].title(), basename(event['file']))
class Resources:
def __init__(self, base_path=None):
self.base_path = base_path
self.inc_dirs = []
self.headers = []
self.s_sources = []
self.c_sources = []
self.cpp_sources = []
self.lib_dirs = set([])
self.objects = []
self.libraries = []
# mbed special files
self.lib_builds = []
self.linker_script = None
def add(self, resources):
self.inc_dirs += resources.inc_dirs
self.headers += resources.headers
self.s_sources += resources.s_sources
self.c_sources += resources.c_sources
self.cpp_sources += resources.cpp_sources
self.lib_dirs |= resources.lib_dirs
self.objects += resources.objects
self.libraries += resources.libraries
self.lib_builds += resources.lib_builds
if resources.linker_script is not None:
self.linker_script = resources.linker_script
def relative_to(self, base, dot=False):
for field in ['inc_dirs', 'headers', 's_sources', 'c_sources',
'cpp_sources', 'lib_dirs', 'objects', 'libraries']:
v = [rel_path(f, base, dot) for f in getattr(self, field)]
setattr(self, field, v)
if self.linker_script is not None:
self.linker_script = rel_path(self.linker_script, base, dot)
def win_to_unix(self):
for field in ['inc_dirs', 'headers', 's_sources', 'c_sources',
'cpp_sources', 'lib_dirs', 'objects', 'libraries']:
v = [f.replace('\\', '/') for f in getattr(self, field)]
setattr(self, field, v)
if self.linker_script is not None:
self.linker_script = self.linker_script.replace('\\', '/')
def __str__(self):
s = []
for (label, resources) in (
('Include Directories', self.inc_dirs),
('Headers', self.headers),
('Assembly sources', self.s_sources),
('C sources', self.c_sources),
('C++ sources', self.cpp_sources),
('Library directories', self.lib_dirs),
('Objects', self.objects),
('Libraries', self.libraries)
):
if resources:
s.append('%s:\n ' % label + '\n '.join(resources))
if self.linker_script:
s.append('Linker Script: ' + self.linker_script)
return '\n'.join(s)
class mbedToolchain:
VERBOSE = True
CORTEX_SYMBOLS = {
"Cortex-M3" : ["__CORTEX_M3", "ARM_MATH_CM3"],
"Cortex-M0" : ["__CORTEX_M0", "ARM_MATH_CM0"],
"Cortex-M0+": ["__CORTEX_M0PLUS", "ARM_MATH_CM0"],
"Cortex-M4" : ["__CORTEX_M4", "ARM_MATH_CM4", "__FPU_PRESENT=1"],
}
def __init__(self, target, notify=None):
self.target = target
if notify is not None:
self.notify = notify
else:
self.notify = print_notify
self.name = self.__class__.__name__
self.obj_path = join(target.name, self.name)
self.symbols = None
self.labels = None
self.build_all = False
def get_symbols(self):
if self.symbols is None:
# Target and Toolchain symbols
labels = self.get_labels()
self.symbols = ["TARGET_%s" % t for t in labels['TARGET']]
self.symbols.extend(["TOOLCHAIN_%s" % t for t in labels['TOOLCHAIN']])
# Cortex CPU symbols
if self.target.core in mbedToolchain.CORTEX_SYMBOLS:
self.symbols.extend(mbedToolchain.CORTEX_SYMBOLS[self.target.core])
return self.symbols
def get_labels(self):
if self.labels is None:
toolchain_labels = [c.__name__ for c in getmro(self.__class__)]
toolchain_labels.remove('mbedToolchain')
self.labels = {
'TARGET': self.target.get_labels(),
'TOOLCHAIN': toolchain_labels
}
return self.labels
def need_update(self, target, dependencies):
if self.build_all:
return True
if not exists(target):
return True
target_mod_time = stat(target).st_mtime
for d in dependencies:
# Some objects are not provided with full path and here we do not have
# information about the library paths. Safe option: assume an update
if not d or not exists(d):
return True
if stat(d).st_mtime >= target_mod_time:
return True
return False
def scan_resources(self, path):
labels = self.get_labels()
resources = Resources(path)
""" os.walk(top[, topdown=True[, onerror=None[, followlinks=False]]])
When topdown is True, the caller can modify the dirnames list in-place
(perhaps using del or slice assignment), and walk() will only recurse into
the subdirectories whose names remain in dirnames; this can be used to prune
the search, impose a specific order of visiting, or even to inform walk()
about directories the caller creates or renames before it resumes walk()
again. Modifying dirnames when topdown is False is ineffective, because in
bottom-up mode the directories in dirnames are generated before dirpath
itself is generated.
"""
for root, dirs, files in walk(path):
# Remove ignored directories
for d in copy(dirs):
if ((d.startswith('.') or d in set(['CVS'])) or
(d.startswith('TARGET_') and d[7:] not in labels['TARGET']) or
(d.startswith('TOOLCHAIN_') and d[10:] not in labels['TOOLCHAIN'])):
dirs.remove(d)
# Add root to include paths
resources.inc_dirs.append(root)
for file in files:
file_path = join(root, file)
_, ext = splitext(file)
ext = ext.lower()
if ext == '.s':
resources.s_sources.append(file_path)
elif ext == '.c':
resources.c_sources.append(file_path)
elif ext == '.cpp':
resources.cpp_sources.append(file_path)
elif ext == '.h':
resources.headers.append(file_path)
elif ext == '.o':
resources.objects.append(file_path)
elif ext == self.LIBRARY_EXT:
resources.libraries.append(file_path)
resources.lib_dirs.add(root)
elif ext == self.LINKER_EXT:
resources.linker_script = file_path
elif ext == '.bld':
resources.lib_builds.append(file_path)
return resources
def copy_files(self, files_paths, trg_path, rel_path=None):
# Handle a single file
if type(files_paths) != ListType: files_paths = [files_paths]
for source in files_paths:
if rel_path is not None:
relative_path = relpath(source, rel_path)
else:
_, relative_path = split(source)
target = join(trg_path, relative_path)
if (target != source) and (self.need_update(target, [source])):
self.progress("copy", relative_path)
mkdir(dirname(target))
copyfile(source, target)
def relative_object_path(self, build_path, base_dir, source):
source_dir, name, _ = split_path(source)
obj_dir = join(build_path, relpath(source_dir, base_dir))
mkdir(obj_dir)
return join(obj_dir, name + '.o')
def compile_sources(self, resources, build_path, inc_dirs=None):
# Web IDE progress bar for project build
self.to_be_compiled = len(resources.s_sources) + len(resources.c_sources) + len(resources.cpp_sources)
self.compiled = 0
objects = []
inc_paths = resources.inc_dirs
if inc_dirs is not None:
inc_paths.extend(inc_dirs)
base_path = resources.base_path
for source in resources.s_sources:
self.compiled += 1
object = self.relative_object_path(build_path, base_path, source)
if self.need_update(object, [source]):
self.progress("assemble", source, build_update=True)
self.assemble(source, object)
objects.append(object)
# The dependency checking for C/C++ is delegated to the specific compiler
for source in resources.c_sources:
object = self.relative_object_path(build_path, base_path, source)
self.compile_c(source, object, inc_paths)
objects.append(object)
for source in resources.cpp_sources:
object = self.relative_object_path(build_path, base_path, source)
self.compile_cpp(source, object, inc_paths)
objects.append(object)
return objects
def compile(self, cc, source, object, includes):
# Check dependencies
base, _ = splitext(object)
dep_path = base + '.d'
self.compiled += 1
if (not exists(dep_path) or
self.need_update(object, self.parse_dependencies(dep_path))):
self.progress("compile", source, build_update=True)
# Compile
command = cc + ['-D%s' % s for s in self.get_symbols()] + ["-I%s" % i for i in includes] + ["-o", object, source]
if hasattr(self, "get_dep_opt"):
command.extend(self.get_dep_opt(dep_path))
if hasattr(self, "cc_extra"):
command.extend(self.cc_extra(base))
self.debug(command)
_, stderr, rc = run_cmd(command, dirname(object))
# Parse output for Warnings and Errors
self.parse_output(stderr)
# Check return code
if rc != 0:
raise ToolException(stderr)
def compile_c(self, source, object, includes):
self.compile(self.cc, source, object, includes)
def compile_cpp(self, source, object, includes):
self.compile(self.cppc, source, object, includes)
def build_library(self, objects, dir, name):
lib = self.STD_LIB_NAME % name
fout = join(dir, lib)
if self.need_update(fout, objects):
self.info("Library: %s" % lib)
self.archive(objects, fout)
def build_program(self, r, tmp_path, name):
objects = self.compile_sources(r, tmp_path) + r.objects
elf = join(tmp_path, name + '.elf')
bin = join(tmp_path, name + '.bin')
if self.need_update(elf, objects + r.libraries + [r.linker_script]):
self.progress("link", name)
self.link(elf, objects, r.libraries, r.lib_dirs, r.linker_script)
if self.need_update(bin, [elf]):
self.progress("elf2bin", name)
self.binary(elf, bin)
if self.target.name.startswith('LPC'):
self.debug("LPC Patch %s" % (name + '.bin'))
patch(bin)
self.var("compile_succeded", True)
self.var("binary", name+'.bin')
return bin
def default_cmd(self, command):
self.debug(command)
stdout, stderr, rc = run_cmd(command)
self.debug(stdout)
if rc != 0:
for line in stderr.splitlines():
self.tool_error(line)
raise ToolException(stderr)
### NOTIFICATIONS ###
def info(self, message):
self.notify({'type': 'info', 'message': message})
def debug(self, message):
if self.VERBOSE:
if type(message) is ListType:
message = ' '.join(message)
self.notify({'type': 'debug', 'message': message})
def cc_info(self, severity, file, line, message):
self.notify({'type': 'cc', 'severity': severity, 'file': file, 'line': line, 'message': message})
def progress(self, action, file, build_update=False):
msg = {'type': 'progress', 'action': action, 'file': file}
if build_update:
msg['percent'] = 100. * float(self.compiled) / float(self.to_be_compiled)
self.notify(msg)
def tool_error(self, message):
self.notify({'type': 'tool_error', 'message': message})
def var(self, key, value):
self.notify({'type': 'var', 'key': key, 'val': value})
from workspace_tools.toolchains.arm import ARM_STD, ARM_MICRO
from workspace_tools.toolchains.gcc import GCC_ARM, GCC_CS, GCC_CR, GCC_CW_EWL, GCC_CW_NEWLIB
from workspace_tools.toolchains.iar import IAR
TOOLCHAIN_CLASSES = {
'ARM': ARM_STD, 'uARM': ARM_MICRO,
'GCC_ARM': GCC_ARM, 'GCC_CS': GCC_CS, 'GCC_CR': GCC_CR,
'GCC_CW_EWL': GCC_CW_EWL, 'GCC_CW_NEWLIB': GCC_CW_NEWLIB,
'IAR': IAR
}
TOOLCHAINS = set(TOOLCHAIN_CLASSES.keys())