Anders Blomdell / mbed-sdk-tools
Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers arm.py Source File

arm.py

00001 """
00002 mbed SDK
00003 Copyright (c) 2011-2013 ARM Limited
00004 
00005 Licensed under the Apache License, Version 2.0 (the "License");
00006 you may not use this file except in compliance with the License.
00007 You may obtain a copy of the License at
00008 
00009     http://www.apache.org/licenses/LICENSE-2.0
00010 
00011 Unless required by applicable law or agreed to in writing, software
00012 distributed under the License is distributed on an "AS IS" BASIS,
00013 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00014 See the License for the specific language governing permissions and
00015 limitations under the License.
00016 """
00017 from __future__ import print_function, absolute_import
00018 from builtins import str
00019 
00020 import re
00021 from copy import copy
00022 from os.path import join, dirname, splitext, basename, exists, relpath, isfile
00023 from os import makedirs, write, curdir, remove
00024 from tempfile import mkstemp
00025 from shutil import rmtree
00026 from distutils.version import LooseVersion
00027 
00028 from tools.targets import CORE_ARCH
00029 from tools.toolchains import mbedToolchain, TOOLCHAIN_PATHS
00030 from tools.hooks import hook_tool
00031 from tools.utils import mkdir, NotSupportedException, run_cmd
00032 
00033 class ARM(mbedToolchain):
00034     LINKER_EXT = '.sct'
00035     LIBRARY_EXT = '.ar'
00036 
00037     STD_LIB_NAME = "%s.ar"
00038     DIAGNOSTIC_PATTERN  = re.compile('"(?P<file>[^"]+)", line (?P<line>\d+)( \(column (?P<column>\d+)\)|): (?P<severity>Warning|Error|Fatal error): (?P<message>.+)')
00039     INDEX_PATTERN  = re.compile('(?P<col>\s*)\^')
00040     DEP_PATTERN = re.compile('\S+:\s(?P<file>.+)\n')
00041     SHEBANG = "#! armcc -E"
00042     SUPPORTED_CORES = ["Cortex-M0", "Cortex-M0+", "Cortex-M3", "Cortex-M4",
00043                        "Cortex-M4F", "Cortex-M7", "Cortex-M7F", "Cortex-M7FD", "Cortex-A9"]
00044     ARMCC_RANGE = (LooseVersion("5.06"), LooseVersion("5.07"))
00045     ARMCC_VERSION_RE = re.compile(b"Component: ARM Compiler (\d+\.\d+)")
00046 
00047     @staticmethod
00048     def check_executable():
00049         """Returns True if the executable (armcc) location specified by the
00050          user exists OR the executable can be found on the PATH.
00051          Returns False otherwise."""
00052         return mbedToolchain.generic_check_executable("ARM", 'armcc', 2, 'bin')
00053 
00054     def __init__(self, target, notify=None, macros=None,
00055                  build_profile=None, build_dir=None):
00056         mbedToolchain.__init__(
00057             self, target, notify, macros, build_dir=build_dir,
00058             build_profile=build_profile)
00059         if target.core not in self.SUPPORTED_CORES:
00060             raise NotSupportedException(
00061                 "this compiler does not support the core %s" % target.core)
00062 
00063         if getattr(target, "default_lib", "std") == "small":
00064             if "-DMBED_RTOS_SINGLE_THREAD" not in self.flags['common']:
00065                 self.flags['common'].append("-DMBED_RTOS_SINGLE_THREAD")
00066             if "-D__MICROLIB" not in self.flags['common']:
00067                 self.flags['common'].append("-D__MICROLIB")
00068             if "--library_type=microlib" not in self.flags['ld']:
00069                 self.flags['ld'].append("--library_type=microlib")
00070             if "--library_type=microlib" not in self.flags['common']:
00071                 self.flags['common'].append("--library_type=microlib")
00072 
00073         if target.core == "Cortex-M0+":
00074             cpu = "Cortex-M0"
00075         elif target.core == "Cortex-M4F":
00076             cpu = "Cortex-M4.fp"
00077         elif target.core == "Cortex-M7FD":
00078             cpu = "Cortex-M7.fp.dp"
00079         elif target.core == "Cortex-M7F":
00080             cpu = "Cortex-M7.fp.sp"
00081         else:
00082             cpu = target.core
00083 
00084         ARM_BIN = join(TOOLCHAIN_PATHS['ARM'], "bin")
00085         ARM_INC = join(TOOLCHAIN_PATHS['ARM'], "include")
00086 
00087         main_cc = join(ARM_BIN, "armcc")
00088 
00089         self.flags['common'] += ["--cpu=%s" % cpu]
00090 
00091         self.asm = [main_cc] + self.flags['common'] + self.flags['asm']
00092         self.cc = [main_cc] + self.flags['common'] + self.flags['c']
00093         self.cppc = [main_cc] + self.flags['common'] + self.flags['c'] + self.flags['cxx']
00094 
00095         self.ld = [join(ARM_BIN, "armlink")] + self.flags['ld']
00096 
00097         self.ar = join(ARM_BIN, "armar")
00098         self.elf2bin = join(ARM_BIN, "fromelf")
00099 
00100         self.SHEBANG += " --cpu=%s" % cpu
00101 
00102     def version_check(self):
00103         stdout, _, retcode = run_cmd([self.cc[0], "--vsn"], redirect=True)
00104         msg = None
00105         min_ver, max_ver = self.ARMCC_RANGE
00106         match = self.ARMCC_VERSION_RE.search(stdout)
00107         found_version = LooseVersion(match.group(1).decode("utf-8")) if match else None
00108         min_ver, max_ver = self.ARMCC_RANGE
00109         if found_version and (found_version < min_ver or found_version >= max_ver):
00110             msg = ("Compiler version mismatch: Have {}; "
00111                    "expected version >= {} and < {}"
00112                    .format(found_version, min_ver, max_ver))
00113         elif not match or len(match.groups()) != 1:
00114             msg = ("Compiler version mismatch: Could not detect version; "
00115                    "expected version >= {} and < {}"
00116                    .format(min_ver, max_ver))
00117 
00118         if msg:
00119             self.notify.cc_info({
00120                 "message": msg,
00121                 "file": "",
00122                 "line": "",
00123                 "col": "",
00124                 "severity": "ERROR",
00125             })
00126 
00127     def _get_toolchain_labels(self):
00128         if getattr(self.target, "default_lib", "std") == "small":
00129             return ["ARM", "ARM_MICRO"]
00130         else:
00131             return ["ARM", "ARM_STD"]
00132 
00133     def parse_dependencies(self, dep_path):
00134         dependencies = []
00135         for line in open(dep_path).readlines():
00136             match = ARM.DEP_PATTERN.match(line)
00137             if match is not None:
00138                 #we need to append chroot, because when the .d files are generated the compiler is chrooted
00139                 dependencies.append((self.CHROOT if self.CHROOT else '') + match.group('file'))
00140         return dependencies
00141 
00142     def parse_output(self, output):
00143         msg = None
00144         for line in output.splitlines():
00145             match = ARM.DIAGNOSTIC_PATTERN.match(line)
00146             if match is not None:
00147                 if msg is not None:
00148                     self.notify.cc_info(msg)
00149                     msg = None
00150                 msg = {
00151                     'severity': match.group('severity').lower(),
00152                     'file': match.group('file'),
00153                     'line': match.group('line'),
00154                     'col': match.group('column') if match.group('column') else 0,
00155                     'message': match.group('message'),
00156                     'text': '',
00157                     'target_name': self.target.name,
00158                     'toolchain_name': self.name
00159                 }
00160             elif msg is not None:
00161                 # Determine the warning/error column by calculating the ^ position
00162                 match = ARM.INDEX_PATTERN.match(line)
00163                 if match is not None:
00164                     msg['col'] = len(match.group('col'))
00165                     self.notify.cc_info(msg)
00166                     msg = None
00167                 else:
00168                     msg['text'] += line+"\n"
00169         
00170         if msg is not None:
00171             self.notify.cc_info(msg)
00172 
00173     def get_dep_option(self, object):
00174         base, _ = splitext(object)
00175         dep_path = base + '.d'
00176         return ["--depend", dep_path]
00177 
00178     def get_config_option(self, config_header):
00179         return ['--preinclude=' + config_header]
00180 
00181     def get_compile_options(self, defines, includes, for_asm=False):
00182         opts = ['-D%s' % d for d in defines]
00183         config_header = self.get_config_header()
00184         if config_header is not None:
00185             opts = opts + self.get_config_option(config_header)
00186         if for_asm:
00187             return opts
00188         if self.RESPONSE_FILES:
00189             opts += ['--via', self.get_inc_file(includes)]
00190         else:
00191             opts += ["-I%s" % i for i in includes]
00192 
00193         return opts
00194 
00195     @hook_tool
00196     def assemble(self, source, object, includes):
00197         # Preprocess first, then assemble
00198         dir = join(dirname(object), '.temp')
00199         mkdir(dir)
00200         tempfile = join(dir, basename(object) + '.E.s')
00201 
00202         # Build preprocess assemble command
00203         cmd_pre = copy(self.asm)
00204         cmd_pre.extend(self.get_compile_options(
00205             self.get_symbols(True), includes, True))
00206         cmd_pre.extend(["-E", "-o", tempfile, source])
00207 
00208         # Build main assemble command
00209         cmd = self.asm + ["-o", object, tempfile]
00210 
00211         # Call cmdline hook
00212         cmd_pre = self.hook.get_cmdline_assembler(cmd_pre)
00213         cmd = self.hook.get_cmdline_assembler(cmd)
00214 
00215         # Return command array, don't execute
00216         return [cmd_pre, cmd]
00217 
00218     @hook_tool
00219     def compile(self, cc, source, object, includes):
00220         # Build compile command
00221         cmd = cc + self.get_compile_options(self.get_symbols(), includes)
00222 
00223         cmd.extend(self.get_dep_option(object))
00224 
00225         cmd.extend(["-o", object, source])
00226 
00227         # Call cmdline hook
00228         cmd = self.hook.get_cmdline_compiler(cmd)
00229 
00230         return [cmd]
00231 
00232     def compile_c(self, source, object, includes):
00233         return self.compile(self.cc, source, object, includes)
00234 
00235     def compile_cpp(self, source, object, includes):
00236         return self.compile(self.cppc, source, object, includes)
00237 
00238     def correct_scatter_shebang(self, scatter_file, cur_dir_name=None):
00239         """Correct the shebang at the top of a scatter file.
00240 
00241         Positional arguments:
00242         scatter_file -- the scatter file to correct
00243 
00244         Keyword arguments:
00245         cur_dir_name -- the name (not path) of the directory containing the
00246                         scatter file
00247 
00248         Return:
00249         The location of the correct scatter file
00250 
00251         Side Effects:
00252         This method MAY write a new scatter file to disk
00253         """
00254         with open(scatter_file, "r") as input:
00255             lines = input.readlines()
00256             if (lines[0].startswith(self.SHEBANG) or
00257                 not lines[0].startswith("#!")):
00258                 return scatter_file
00259             else:
00260                 new_scatter = join(self.build_dir, ".link_script.sct")
00261                 if cur_dir_name is None:
00262                     cur_dir_name = dirname(scatter_file)
00263                 self.SHEBANG += " -I %s" % cur_dir_name
00264                 if self.need_update(new_scatter, [scatter_file]):
00265                     with open(new_scatter, "w") as out:
00266                         out.write(self.SHEBANG)
00267                         out.write("\n")
00268                         out.write("".join(lines[1:]))
00269 
00270                 return new_scatter
00271 
00272     @hook_tool
00273     def link(self, output, objects, libraries, lib_dirs, scatter_file):
00274         base, _ = splitext(output)
00275         map_file = base + ".map"
00276         args = ["-o", output, "--info=totals", "--map", "--list=%s" % map_file]
00277         args.extend(objects)
00278         args.extend(libraries)
00279         if lib_dirs:
00280             args.extend(["--userlibpath", ",".join(lib_dirs)])
00281         if scatter_file:
00282             new_scatter = self.correct_scatter_shebang(scatter_file)
00283             args.extend(["--scatter", new_scatter])
00284 
00285         cmd_pre = self.ld + args
00286         cmd = self.hook.get_cmdline_linker(cmd_pre)
00287 
00288         if self.RESPONSE_FILES:
00289             cmd_linker = cmd[0]
00290             link_files = self.get_link_file(cmd[1:])
00291             cmd = [cmd_linker, '--via', link_files]
00292 
00293         self.notify.cc_verbose("Link: %s" % ' '.join(cmd))
00294         self.default_cmd(cmd)
00295 
00296     @hook_tool
00297     def archive(self, objects, lib_path):
00298         if self.RESPONSE_FILES:
00299             param = ['--via', self.get_arch_file(objects)]
00300         else:
00301             param = objects
00302         self.default_cmd([self.ar, '-r', lib_path] + param)
00303 
00304     @hook_tool
00305     def binary(self, resources, elf, bin):
00306         _, fmt = splitext(bin)
00307         # On .hex format, combine multiple .hex files (for multiple load regions) into one 
00308         bin_arg = {".bin": "--bin", ".hex": "--i32combined"}[fmt]
00309         cmd = [self.elf2bin, bin_arg, '-o', bin, elf]
00310         cmd = self.hook.get_cmdline_binary(cmd)
00311 
00312         # remove target binary file/path
00313         if exists(bin):
00314             if isfile(bin):
00315                 remove(bin)
00316             else:
00317                 rmtree(bin)
00318 
00319         self.notify.cc_verbose("FromELF: %s" % ' '.join(cmd))
00320         self.default_cmd(cmd)
00321 
00322     @staticmethod
00323     def name_mangle(name):
00324         return "_Z%i%sv" % (len(name), name)
00325 
00326     @staticmethod
00327     def make_ld_define(name, value):
00328         return "--predefine=\"-D%s=%s\"" % (name, value)
00329 
00330     @staticmethod
00331     def redirect_symbol(source, sync, build_dir):
00332         if not exists(build_dir):
00333             makedirs(build_dir)
00334         handle, filename = mkstemp(prefix=".redirect-symbol.", dir=build_dir)
00335         write(handle, "RESOLVE %s AS %s\n" % (source, sync))
00336         return "--edit=%s" % filename
00337 
00338 
00339 class ARM_STD(ARM):
00340     def __init__(self, target, notify=None, macros=None,
00341                  build_profile=None, build_dir=None):
00342         ARM.__init__(self, target, notify, macros, build_dir=build_dir,
00343                      build_profile=build_profile)
00344         if not set(("ARM", "uARM")).intersection(set(target.supported_toolchains)):
00345             raise NotSupportedException("ARM/uARM compiler support is required for ARM build")
00346 
00347 
00348 class ARM_MICRO(ARM):
00349     PATCHED_LIBRARY = False
00350     def __init__(self, target, notify=None, macros=None,
00351                  silent=False, extra_verbose=False, build_profile=None,
00352                  build_dir=None):
00353         target.default_lib = "small"
00354         ARM.__init__(self, target, notify, macros, build_dir=build_dir,
00355                      build_profile=build_profile)
00356         if not set(("ARM", "uARM")).intersection(set(target.supported_toolchains)):
00357             raise NotSupportedException("ARM/uARM compiler support is required for ARM build")
00358 
00359 class ARMC6(ARM_STD):
00360     SHEBANG = "#! armclang -E --target=arm-arm-none-eabi -x c"
00361     SUPPORTED_CORES = ["Cortex-M0", "Cortex-M0+", "Cortex-M3", "Cortex-M4",
00362                        "Cortex-M4F", "Cortex-M7", "Cortex-M7F", "Cortex-M7FD",
00363                        "Cortex-M23", "Cortex-M23-NS", "Cortex-M33",
00364                        "Cortex-M33-NS", "Cortex-A9"]
00365     ARMCC_RANGE = (LooseVersion("6.10"), LooseVersion("7.0"))
00366 
00367     @staticmethod
00368     def check_executable():
00369         return mbedToolchain.generic_check_executable("ARMC6", "armclang", 1)
00370 
00371     def __init__(self, target, *args, **kwargs):
00372         mbedToolchain.__init__(self, target, *args, **kwargs)
00373         if target.core not in self.SUPPORTED_CORES:
00374             raise NotSupportedException(
00375                 "this compiler does not support the core %s" % target.core)
00376         if CORE_ARCH[target.core] < 8:
00377             self.notify.cc_info({
00378                 'severity': "Error", 'file': "", 'line': "", 'col': "",
00379                 'message': "ARMC6 does not support ARM architecture v{}"
00380                 " targets".format(CORE_ARCH[target.core]),
00381                 'text': '', 'target_name': self.target.name,
00382                 'toolchain_name': self.name
00383             })
00384 
00385         if not set(("ARM", "ARMC6")).intersection(set(target.supported_toolchains)):
00386             raise NotSupportedException("ARM/ARMC6 compiler support is required for ARMC6 build")
00387 
00388         if target.core.lower().endswith("fd"):
00389             self.flags['common'].append("-mcpu=%s" % target.core.lower()[:-2])
00390             self.flags['ld'].append("--cpu=%s" % target.core.lower()[:-2])
00391             self.SHEBANG += " -mcpu=%s" % target.core.lower()[:-2]
00392         elif target.core.lower().endswith("f"):
00393             self.flags['common'].append("-mcpu=%s" % target.core.lower()[:-1])
00394             self.flags['ld'].append("--cpu=%s" % target.core.lower()[:-1])
00395             self.SHEBANG += " -mcpu=%s" % target.core.lower()[:-1]
00396         elif target.core.startswith("Cortex-M33"):
00397             self.flags['common'].append("-mcpu=cortex-m33+nodsp")
00398             self.flags['common'].append("-mfpu=none")
00399             self.flags['ld'].append("--cpu=Cortex-M33.no_dsp.no_fp")
00400         elif not target.core.startswith("Cortex-M23"):
00401             self.flags['common'].append("-mcpu=%s" % target.core.lower())
00402             self.flags['ld'].append("--cpu=%s" % target.core.lower())
00403             self.SHEBANG += " -mcpu=%s" % target.core.lower()
00404 
00405         if target.core == "Cortex-M4F":
00406             self.flags['common'].append("-mfpu=fpv4-sp-d16")
00407             self.flags['common'].append("-mfloat-abi=hard")
00408         elif target.core == "Cortex-M7F":
00409             self.flags['common'].append("-mfpu=fpv5-sp-d16")
00410             self.flags['common'].append("-mfloat-abi=softfp")
00411         elif target.core == "Cortex-M7FD":
00412             self.flags['common'].append("-mfpu=fpv5-d16")
00413             self.flags['common'].append("-mfloat-abi=softfp")
00414         elif target.core.startswith("Cortex-M23"):
00415             self.flags['common'].append("-march=armv8-m.base")
00416 
00417         if target.core == "Cortex-M23" or target.core == "Cortex-M33":
00418             self.flags['cxx'].append("-mcmse")
00419             self.flags['c'].append("-mcmse")
00420 
00421         # Create Secure library
00422         if ((target.core == "Cortex-M23" or self.target.core == "Cortex-M33") and
00423             kwargs.get('build_dir', False)):
00424             build_dir = kwargs['build_dir']
00425             secure_file = join(build_dir, "cmse_lib.o")
00426             self.flags["ld"] += ["--import_cmse_lib_out=%s" % secure_file]
00427         # Add linking time preprocessor macro __DOMAIN_NS
00428         if target.core == "Cortex-M23-NS" or self.target.core == "Cortex-M33-NS":
00429             define_string = self.make_ld_define("__DOMAIN_NS", "0x1")
00430             self.flags["ld"].append(define_string)
00431 
00432         asm_cpu = {
00433             "Cortex-M0+": "Cortex-M0",
00434             "Cortex-M4F": "Cortex-M4.fp",
00435             "Cortex-M7F": "Cortex-M7.fp.sp",
00436             "Cortex-M7FD": "Cortex-M7.fp.dp",
00437             "Cortex-M23-NS": "Cortex-M23",
00438             "Cortex-M33-NS": "Cortex-M33" }.get(target.core, target.core)
00439 
00440         if target.core.startswith("Cortex-M33"):
00441             self.flags['asm'].append("--cpu=Cortex-M33.no_dsp.no_fp")
00442         else :
00443             self.flags['asm'].append("--cpu=%s" % asm_cpu)
00444 
00445         self.cc = ([join(TOOLCHAIN_PATHS["ARMC6"], "armclang")] +
00446                    self.flags['common'] + self.flags['c'])
00447         self.cppc = ([join(TOOLCHAIN_PATHS["ARMC6"], "armclang")] +
00448                      self.flags['common'] + self.flags['cxx'])
00449         self.asm = [join(TOOLCHAIN_PATHS["ARMC6"], "armasm")] + self.flags['asm']
00450         self.ld = [join(TOOLCHAIN_PATHS["ARMC6"], "armlink")] + self.flags['ld']
00451         self.ar = [join(TOOLCHAIN_PATHS["ARMC6"], "armar")]
00452         self.elf2bin = join(TOOLCHAIN_PATHS["ARMC6"], "fromelf")
00453 
00454     def _get_toolchain_labels(self):
00455         return ["ARM", "ARM_STD", "ARMC6"]
00456 
00457     def parse_dependencies(self, dep_path):
00458         return mbedToolchain.parse_dependencies(self, dep_path)
00459 
00460     def is_not_supported_error(self, output):
00461         return "#error [NOT_SUPPORTED]" in output
00462 
00463     def parse_output(self, output):
00464         pass
00465 
00466     def get_config_option(self, config_header):
00467         return ["-include", config_header]
00468 
00469     def get_compile_options(self, defines, includes, for_asm=False):
00470         opts = ['-D%s' % d for d in defines]
00471         opts.extend(["-I%s" % i for i in includes])
00472         if for_asm:
00473             return ["--cpreproc",
00474                     "--cpreproc_opts=%s" % ",".join(self.flags['common'] + opts)]
00475         else:
00476             config_header = self.get_config_header()
00477             if config_header:
00478                 opts.extend(self.get_config_option(config_header))
00479             return opts
00480 
00481     @hook_tool
00482     def assemble(self, source, object, includes):
00483         cmd_pre = copy(self.asm)
00484         cmd_pre.extend(self.get_compile_options(
00485             self.get_symbols(True), includes, for_asm=True))
00486         cmd_pre.extend(["-o", object, source])
00487         return [self.hook.get_cmdline_assembler(cmd_pre)]
00488 
00489     @hook_tool
00490     def compile(self, cc, source, object, includes):
00491         cmd = copy(cc)
00492         cmd.extend(self.get_compile_options(self.get_symbols(), includes))
00493         cmd.extend(["-o", object, source])
00494         cmd = self.hook.get_cmdline_compiler(cmd)
00495         return [cmd]