New export-build tests.

Allows command line workflow of:
mbed import
mbed export

Also revises exporter supported target checks
pull/3227/head
Sarah Marsh 2016-10-31 16:00:15 -05:00 committed by Anna Bridge
parent fe029510c9
commit 027f2b23cc
10 changed files with 235 additions and 98 deletions

View File

@ -31,10 +31,25 @@ class DeviceCMSIS():
Encapsulates target information retrieved by arm-pack-manager""" Encapsulates target information retrieved by arm-pack-manager"""
def __init__(self, target): def __init__(self, target):
cache = Cache(True, False) target_info = self.check_supported(target)
if not target_info:
raise TargetNotSupportedException("Target not supported in CMSIS pack")
self.url = target_info['pdsc_file']
self.pack_url, self.pack_id = ntpath.split(self.url)
self.dname = target_info["_cpu_name"]
self.core = target_info["_core"]
self.dfpu = target_info['processor']['fpu']
self.debug, self.dvendor = self.vendor_debug(target_info['vendor'])
self.dendian = target_info['processor'].get('endianness','Little-endian')
self.debug_svd = target_info.get('debug', '')
self.compile_header = target_info['compile']['header']
self.target_info = target_info
@staticmethod
def check_supported(target):
cache = Cache(True, False)
t = TARGET_MAP[target] t = TARGET_MAP[target]
self.core = t.core
try: try:
cpu_name = t.device_name cpu_name = t.device_name
target_info = cache.index[cpu_name] target_info = cache.index[cpu_name]
@ -42,26 +57,13 @@ class DeviceCMSIS():
except: except:
try: try:
# Try to find the core as a generic CMSIS target # Try to find the core as a generic CMSIS target
cpu_name = self.cpu_cmsis() cpu_name = DeviceCMSIS.cpu_cmsis(t.core)
target_info = cache.index[cpu_name] target_info = cache.index[cpu_name]
except: except:
raise TargetNotSupportedException("Target not in CMSIS packs") return False
target_info["_cpu_name"] = cpu_name
self.target_info = target_info target_info["_core"] = t.core
return target_info
self.url = target_info['pdsc_file']
self.pack_url, self.pack_id = ntpath.split(self.url)
self.dname = cpu_name
self.dfpu = target_info['processor']['fpu']
self.debug, self.dvendor = self.vendor_debug(target_info['vendor'])
self.dendian = target_info['processor'].get('endianness','Little-endian')
self.debug_svd = target_info.get('debug', '')
self.compile_header = target_info['compile']['header']
def check_version(self, filename):
with open(filename) as data_file:
data = json.load(data_file)
return data.get("version", "0") == "0.1.0"
def vendor_debug(self, vendor): def vendor_debug(self, vendor):
reg = "([\w\s]+):?\d*?" reg = "([\w\s]+):?\d*?"
@ -74,9 +76,9 @@ class DeviceCMSIS():
} }
return debug_map.get(vendor_match, "CMSIS-DAP"), vendor_match return debug_map.get(vendor_match, "CMSIS-DAP"), vendor_match
def cpu_cmsis(self): @staticmethod
def cpu_cmsis(cpu):
#Cortex-M4F => ARMCM4_FP, Cortex-M0+ => ARMCM0P #Cortex-M4F => ARMCM4_FP, Cortex-M0+ => ARMCM0P
cpu = self.core
cpu = cpu.replace("Cortex-","ARMC") cpu = cpu.replace("Cortex-","ARMC")
cpu = cpu.replace("+","P") cpu = cpu.replace("+","P")
cpu = cpu.replace("F","_FP") cpu = cpu.replace("F","_FP")

View File

@ -119,13 +119,6 @@ class Exporter(object):
source_files.extend(getattr(self.resources, key)) source_files.extend(getattr(self.resources, key))
return list(set([os.path.dirname(src) for src in source_files])) return list(set([os.path.dirname(src) for src in source_files]))
def check_supported(self):
"""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:
raise TargetNotSupportedException()
return True
def gen_file(self, template_file, data, target_file): def gen_file(self, template_file, data, target_file):
"""Generates a project file from a template using jinja""" """Generates a project file from a template using jinja"""
jinja_loader = FileSystemLoader( jinja_loader = FileSystemLoader(

View File

@ -2,7 +2,7 @@ import os
from os.path import sep, join, exists from os.path import sep, join, exists
from collections import namedtuple from collections import namedtuple
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
from distutils.spawn import find_executable import shutil
import re import re
import sys import sys
@ -29,7 +29,8 @@ class IAR(Exporter):
#iar_definitions.json #iar_definitions.json
TARGETS = [target for target, obj in TARGET_MAP.iteritems() TARGETS = [target for target, obj in TARGET_MAP.iteritems()
if hasattr(obj, 'device_name') and if hasattr(obj, 'device_name') and
obj.device_name in IAR_DEFS.keys()] obj.device_name in IAR_DEFS.keys() and "IAR" in obj.supported_toolchains
and DeviceCMSIS.check_supported(target)]
SPECIAL_TEMPLATES = { SPECIAL_TEMPLATES = {
'rz_a1h' : 'iar/iar_rz_a1h.ewp.tmpl', 'rz_a1h' : 'iar/iar_rz_a1h.ewp.tmpl',
@ -120,22 +121,13 @@ class IAR(Exporter):
self.gen_file('iar/ewd.tmpl', ctx, self.project_name + ".ewd") self.gen_file('iar/ewd.tmpl', ctx, self.project_name + ".ewd")
self.gen_file(self.get_ewp_template(), ctx, self.project_name + ".ewp") self.gen_file(self.get_ewp_template(), ctx, self.project_name + ".ewp")
def build(self): @staticmethod
def build(project_name, clean=True):
""" Build IAR project """ """ Build IAR project """
# > IarBuild [project_path] -build [project_name] # > IarBuild [project_path] -build [project_name]
proj_file = join(self.export_dir, self.project_name + ".ewp")
if find_executable("IarBuild"): proj_file = project_name + ".ewp"
iar_exe = "IarBuild.exe" cmd = ["IarBuild.exe", proj_file, '-build', project_name]
else:
iar_exe = join('C:', sep,
'Program Files (x86)', 'IAR Systems',
'Embedded Workbench 7.5', 'common', 'bin',
'IarBuild.exe')
if not exists(iar_exe):
raise Exception("IarBuild.exe not found. Add to path.")
cmd = [iar_exe, proj_file, '-build', self.project_name]
# IAR does not support a '0' option to automatically use all # IAR does not support a '0' option to automatically use all
# available CPUs, so we use Python's multiprocessing library # available CPUs, so we use Python's multiprocessing library
@ -156,7 +148,14 @@ class IAR(Exporter):
m = re.match(error_re, line) m = re.match(error_re, line)
if m is not None: if m is not None:
num_errors = int(m.group(1)) num_errors = int(m.group(1))
if clean:
os.remove(project_name + ".ewp")
os.remove(project_name + ".ewd")
os.remove(project_name + ".eww")
shutil.rmtree('.build')
if num_errors !=0: if num_errors !=0:
# Seems like something went wrong. # Seems like something went wrong.
raise FailedBuildException("Project: %s build failed with %s erros" % ( return -1
proj_file, num_errors)) return 0

View File

@ -16,7 +16,10 @@ limitations under the License.
""" """
from os.path import splitext, basename, relpath, join, abspath, dirname,\ from os.path import splitext, basename, relpath, join, abspath, dirname,\
exists exists
from os import curdir, getcwd from os import remove
import sys
from subprocess import check_output, CalledProcessError, Popen, PIPE
import shutil
from jinja2.exceptions import TemplateNotFound from jinja2.exceptions import TemplateNotFound
from tools.export.exporters import Exporter from tools.export.exporters import Exporter
from tools.utils import NotSupportedException from tools.utils import NotSupportedException
@ -102,6 +105,38 @@ class Makefile(Exporter):
else: else:
raise NotSupportedException("This make tool is in development") raise NotSupportedException("This make tool is in development")
@staticmethod
def build(project_name, build_log="build_log.txt", project_loc=None, clean=True):
""" Build Make project """
# > Make -C [project directory] -j
cmd = ["make", "-j"]
p = Popen(cmd, stdout=PIPE, stderr=PIPE)
ret = p.communicate()
out, err = ret[0], ret[1]
ret_code = p.returncode
with open(build_log, 'w+') as f:
f.write("=" * 10 + "OUT" + "=" * 10 + "\n")
f.write(out)
f.write("=" * 10 + "ERR" + "=" * 10 + "\n")
f.write(err)
if ret_code == 0:
f.write("SUCCESS")
else:
f.write("FAILURE")
with open(build_log, 'r') as f:
print "\n".join(f.readlines())
sys.stdout.flush()
if clean:
remove("Makefile")
remove(build_log)
if exists('.build'):
shutil.rmtree('.build')
if ret_code != 0:
# Seems like something went wrong.
return -1
return 0
class GccArm(Makefile): class GccArm(Makefile):
"""GCC ARM specific makefile target""" """GCC ARM specific makefile target"""

View File

@ -3,7 +3,7 @@ from os.path import sep, normpath, join, exists
import ntpath import ntpath
import copy import copy
from collections import namedtuple from collections import namedtuple
from distutils.spawn import find_executable import shutil
import subprocess import subprocess
import re import re
@ -117,10 +117,15 @@ class Uvision(Exporter):
project file (.uvprojx). project file (.uvprojx).
The needed information can be viewed in uvision.tmpl The needed information can be viewed in uvision.tmpl
""" """
NAME = 'cmsis' NAME = 'uvision5'
TOOLCHAIN = 'ARM' TOOLCHAIN = 'ARM'
TARGETS = [target for target, obj in TARGET_MAP.iteritems() TARGETS = []
if "ARM" in obj.supported_toolchains] for target, obj in TARGET_MAP.iteritems():
if not ("ARM" in obj.supported_toolchains and hasattr(obj, "device_name")):
continue
if not DeviceCMSIS.check_supported(target):
continue
TARGETS.append(target)
#File associations within .uvprojx file #File associations within .uvprojx file
file_types = {'.cpp': 8, '.c': 1, '.s': 2, file_types = {'.cpp': 8, '.c': 1, '.s': 2,
'.obj': 3, '.o': 3, '.lib': 4, '.obj': 3, '.o': 3, '.lib': 4,
@ -200,35 +205,22 @@ class Uvision(Exporter):
self.gen_file('uvision/uvision.tmpl', ctx, self.project_name+".uvprojx") self.gen_file('uvision/uvision.tmpl', ctx, self.project_name+".uvprojx")
self.gen_file('uvision/uvision_debug.tmpl', ctx, self.project_name + ".uvoptx") self.gen_file('uvision/uvision_debug.tmpl', ctx, self.project_name + ".uvoptx")
def build(self): @staticmethod
ERRORLEVEL = { def build(project_name, log_name='build_log.txt', clean=True):
0: 'success (0 warnings, 0 errors)',
1: 'warnings',
2: 'errors',
3: 'fatal errors',
11: 'cant write to project file',
12: 'device error',
13: 'error writing',
15: 'error reading xml file',
}
success = 0 success = 0
warn = 1 warn = 1
if find_executable("UV4"): cmd = ["UV4.exe", '-r', '-j0', '-o', log_name, project_name+".uvprojx"]
uv_exe = "UV4.exe"
else:
uv_exe = join('C:', sep,
'Keil_v5', 'UV4', 'UV4.exe')
if not exists(uv_exe):
raise Exception("UV4.exe not found. Add to path.")
cmd = [uv_exe, '-r', '-j0', '-o', join(self.export_dir,'build_log.txt'), join(self.export_dir,self.project_name+".uvprojx")]
ret_code = subprocess.call(cmd) ret_code = subprocess.call(cmd)
with open(join(self.export_dir, 'build_log.txt'), 'r') as build_log: with open(log_name, 'r') as build_log:
print build_log.read() print build_log.read()
if clean:
os.remove(log_name)
os.remove(project_name+".uvprojx")
os.remove(project_name+".uvoptx")
shutil.rmtree(".build")
if ret_code != success and ret_code != warn: if ret_code != success and ret_code != warn:
# Seems like something went wrong. # Seems like something went wrong.
raise FailedBuildException("Project: %s build failed with the status: %s" % ( return -1
self.project_name, ERRORLEVEL.get(ret_code, "Unknown"))) return 0
else:
return "Project: %s build succeeded with the status: %s" % (
self.project_name, ERRORLEVEL.get(ret_code, "Unknown"))

View File

@ -233,7 +233,9 @@ def main():
if (options.program is None) and (not options.source_dir): if (options.program is None) and (not options.source_dir):
args_error(parser, "one of -p, -n, or --source is required") args_error(parser, "one of -p, -n, or --source is required")
# Export to selected toolchain # Export to selected toolchain
_, toolchain_name = get_exporter_toolchain(options.ide) exporter, toolchain_name = get_exporter_toolchain(options.ide)
if options.mcu not in exporter.TARGETS:
args_error(parser, "%s not supported by %s")
profile = extract_profile(parser, options, toolchain_name) profile = extract_profile(parser, options, toolchain_name)
export(options.mcu, options.ide, build=options.build, export(options.mcu, options.ide, build=options.build,
src=options.source_dir, macros=options.macros, src=options.source_dir, macros=options.macros,

View File

@ -86,7 +86,6 @@ def generate_project_files(resources, export_path, target, name, toolchain, ide,
exporter_cls, _ = get_exporter_toolchain(ide) exporter_cls, _ = get_exporter_toolchain(ide)
exporter = exporter_cls(target, export_path, name, toolchain, exporter = exporter_cls(target, export_path, name, toolchain,
extra_symbols=macros, resources=resources) extra_symbols=macros, resources=resources)
exporter.check_supported()
exporter.generate() exporter.generate()
files = exporter.generated_files files = exporter.generated_files
return files, exporter return files, exporter

View File

@ -14,6 +14,62 @@ sys.path.insert(0, ROOT)
from tools.utils import argparse_force_uppercase_type from tools.utils import argparse_force_uppercase_type
import examples_lib as lib import examples_lib as lib
from examples_lib import SUPPORTED_TOOLCHAINS from examples_lib import SUPPORTED_TOOLCHAINS
from tools.export import EXPORTERS
from tools.build_api import get_mbed_official_release
from tools.targets import TARGET_MAP
EXAMPLES = json.load(open(os.path.join(os.path.dirname(__file__),
"examples.json")))
def print_stuff(name, lst):
if lst:
print("#"*80)
print("# {} example combinations".format(name))
print("#")
for thing in lst:
print(thing)
SUPPORTED_TOOLCHAINS = ["ARM", "IAR", "GCC_ARM"]
SUPPORTED_IDES = ["iar", "uvision", "make_gcc_arm", "make_iar", "make_armc5"]
def target_cross_toolchain(allowed_toolchains,
features=[], targets=TARGET_MAP.keys(),
toolchains=SUPPORTED_TOOLCHAINS):
"""Generate pairs of target and toolchains
Args:
allowed_toolchains - a list of all possible toolchains
Kwargs:
features - the features that must be in the features array of a
target
targets - a list of available targets
toolchains - a list of available toolchains
"""
for release_target, release_toolchains in get_mbed_official_release("5"):
for toolchain in release_toolchains:
if (toolchain in allowed_toolchains and
toolchain in toolchains and
release_target in targets and
all(feature in TARGET_MAP[release_target].features
for feature in features)):
yield release_target, toolchain
def target_cross_ide(allowed_ides,
targets=TARGET_MAP.keys()):
"""Generate pairs of target and ides
Args:
allowed_ides - a list of all possible IDEs
"""
for release_target, release_toolchains in get_mbed_official_release("5"):
for ide in allowed_ides:
if release_target in EXPORTERS[ide].TARGETS:
yield release_target, ide
def main(): def main():
@ -27,17 +83,61 @@ def main():
version_cmd.add_argument("tag") version_cmd.add_argument("tag")
version_cmd.set_defaults(fn=do_versionning) version_cmd.set_defaults(fn=do_versionning)
compile_cmd = subparsers.add_parser("compile") compile_cmd = subparsers.add_parser("compile")
compile_cmd.set_defaults(fn=do_compile) compile_cmd.set_defaults(fn=do_compile),
compile_cmd.add_argument( compile_cmd.add_argument(
"toolchains", nargs="*", default=SUPPORTED_TOOLCHAINS, "toolchains", nargs="*", default=SUPPORTED_TOOLCHAINS,
type=argparse_force_uppercase_type(SUPPORTED_TOOLCHAINS, type=argparse_force_uppercase_type(SUPPORTED_TOOLCHAINS,
"toolchain")) "toolchain")),
export_cmd = subparsers.add_parser("export")
export_cmd.set_defaults(fn=do_export),
export_cmd.add_argument(
"ide", nargs="*", default=SUPPORTED_IDES,
type=argparse_force_uppercase_type(SUPPORTED_IDES,
"ide"))
args = parser.parse_args() args = parser.parse_args()
config = json.load(open(os.path.join(os.path.dirname(__file__), config = json.load(open(os.path.join(os.path.dirname(__file__),
args.config))) args.config)))
return args.fn(args, config) return args.fn(args, config)
def do_export(args):
def print_message(message, name):
print(message+ " %s"%name)
sys.stdout.flush()
export_failures = []
build_failures = []
sucesses = []
for example, requirements in EXAMPLES.iteritems():
ex_name = basename(example)
if ex_name != "mbed-os-example-blinky":
continue
os.chdir(ex_name)
for target, ide in target_cross_ide(args.ide):
example_name = "{} {} {}".format(ex_name, target,
ide)
print_message("Export:",example_name)
proc = subprocess.Popen(["mbed-cli", "export", "-i", ide,
"-m", target])
proc.wait()
if proc.returncode:
export_failures.append(example_name)
print_message("FAILURE Export:", example_name)
else:
print_message("SUCCESS Export:", example_name)
print_message("Build:", example_name)
if EXPORTERS[ide].build(ex_name):
print_message("FAILURE Build:", example_name)
build_failures.append(example_name)
else:
print_message("SUCCESS Build:", example_name)
sucesses.append(example_name)
print_stuff("Passed", sucesses)
print_stuff("Failed Export", export_failures)
print_stuff("Failed Building", build_failures)
return len(export_failures+build_failures)
def do_import(_, config): def do_import(_, config):
"""Do the import step of this process""" """Do the import step of this process"""

View File

@ -81,6 +81,19 @@ def target_cross_toolchain(allowed_toolchains,
for feature in features)): for feature in features)):
yield target, toolchain yield target, toolchain
def target_cross_ide(allowed_ides,
targets=TARGET_MAP.keys()):
"""Generate pairs of target and ides
Args:
allowed_ides - a list of all possible IDEs
"""
for release_target, release_toolchains in get_mbed_official_release("5"):
for ide in allowed_ides:
if release_target in EXPORTERS[ide].TARGETS:
yield release_target, ide
def get_repo_list(example): def get_repo_list(example):
""" Returns a list of all the repos associated with the specific example in the json """ Returns a list of all the repos associated with the specific example in the json

View File

@ -22,6 +22,7 @@ from os.path import join, dirname, exists, abspath
ROOT = abspath(join(dirname(__file__), "..", "..", "..")) ROOT = abspath(join(dirname(__file__), "..", "..", ".."))
sys.path.insert(0, ROOT) sys.path.insert(0, ROOT)
import argparse import argparse
import os
from argparse import ArgumentTypeError from argparse import ArgumentTypeError
import sys import sys
from shutil import rmtree from shutil import rmtree
@ -37,9 +38,8 @@ from tools.project import export
from Queue import Queue from Queue import Queue
from threading import Thread, Lock from threading import Thread, Lock
from tools.project_api import print_results, get_exporter_toolchain from tools.project_api import print_results, get_exporter_toolchain
from tools.tests import test_name_known, test_known, Test from tools.tests import test_name_known, test_known
from tools.export.exporters import FailedBuildException, \ from tools.export import EXPORTERS
TargetNotSupportedException
from tools.utils import argparse_force_lowercase_type, \ from tools.utils import argparse_force_lowercase_type, \
argparse_many, columnate, args_error, \ argparse_many, columnate, args_error, \
argparse_filestring_type argparse_filestring_type
@ -125,9 +125,12 @@ class ExportBuildTest(object):
% (test_case.mcu, % (test_case.mcu,
test_case.ide, test_case.ide,
test_case.name)) test_case.name))
try:
exporter.build() cwd = os.getcwd()
except FailedBuildException: os.chdir(exporter.export_dir)
res = EXPORTERS[exporter.NAME.lower()].build(exporter.project_name, clean=False)
os.chdir(cwd)
if res:
self.failures.append("%s::%s\t%s" % (test_case.mcu, self.failures.append("%s::%s\t%s" % (test_case.mcu,
test_case.ide, test_case.ide,
test_case.name)) test_case.name))
@ -157,20 +160,19 @@ class ExportBuildTest(object):
self.display_counter("Exporting test case %s::%s\t%s" % (test_case.mcu, self.display_counter("Exporting test case %s::%s\t%s" % (test_case.mcu,
test_case.ide, test_case.ide,
test_case.name)) test_case.name))
exporter, toolchain = get_exporter_toolchain(test_case.ide)
try: if test_case.mcu not in exporter.TARGETS:
_, toolchain = get_exporter_toolchain(test_case.ide) self.skips.append("%s::%s\t%s" % (test_case.mcu, test_case.ide,
profile = extract_profile(self.parser, self.options, toolchain) test_case.name))
exporter = export(test_case.mcu, test_case.ide, return
profile = extract_profile(self.parser, self.options, toolchain)
exporter = export(test_case.mcu, test_case.ide,
project_id=test_case.id, zip_proj=None, project_id=test_case.id, zip_proj=None,
clean=True, src=test_case.src, clean=True, src=test_case.src,
export_path=join(EXPORT_DIR,name_str), export_path=join(EXPORT_DIR,name_str),
silent=True, build_profile=profile) silent=True, build_profile=profile)
exporter.generated_files.append(join(EXPORT_DIR,name_str,test_case.log)) exporter.generated_files.append(join(EXPORT_DIR,name_str,test_case.log))
self.build_queue.put((exporter,test_case)) self.build_queue.put((exporter,test_case))
except TargetNotSupportedException:
self.skips.append("%s::%s\t%s" % (test_case.mcu, test_case.ide,
test_case.name))
# Check if the specified name is in all_os_tests # Check if the specified name is in all_os_tests
@ -265,7 +267,7 @@ def main():
test_targets = options.mcu or targetnames test_targets = options.mcu or targetnames
if not all([t in targetnames for t in test_targets]): if not all([t in targetnames for t in test_targets]):
args_error(parser, "Only specify targets in release %s:\n%s" args_error(parser, "Only specify targets in release %s:\n%s"
%(options.release, columnate(targetnames))) %(options.release, columnate(sorted(targetnames))))
v2_tests, v5_tests = [],[] v2_tests, v5_tests = [],[]
if options.release == '5': if options.release == '5':