Skip to content

Commit 2502a79

Browse files
authored
Merge pull request #3172 from sarahmarshy/export-test-rev
[Exporters] New export-build tests
2 parents 7d31512 + 9624ccf commit 2502a79

File tree

12 files changed

+296
-125
lines changed

12 files changed

+296
-125
lines changed

tools/export/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from tools.export import codered, ds5_5, iar, makefile
1919
from tools.export import emblocks, coide, kds, simplicityv3, atmelstudio
2020
from tools.export import sw4stm32, e2studio, zip, cmsis, uvision, cdt
21-
from tools.export.exporters import OldLibrariesException, FailedBuildException
2221
from tools.targets import TARGET_NAMES
2322

2423
EXPORTERS = {

tools/export/cmsis/__init__.py

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -30,38 +30,41 @@ class DeviceCMSIS():
3030
"""CMSIS Device class
3131
3232
Encapsulates target information retrieved by arm-pack-manager"""
33-
def __init__(self, target):
34-
cache = Cache(True, False)
3533

36-
t = TARGET_MAP[target]
37-
self.core = t.core
38-
try:
39-
cpu_name = t.device_name
40-
target_info = cache.index[cpu_name]
41-
# Target does not have device name or pdsc file
42-
except:
43-
try:
44-
# Try to find the core as a generic CMSIS target
45-
cpu_name = self.cpu_cmsis()
46-
target_info = cache.index[cpu_name]
47-
except:
48-
raise TargetNotSupportedException("Target not in CMSIS packs")
49-
50-
self.target_info = target_info
34+
CACHE = Cache(True, False)
35+
def __init__(self, target):
36+
target_info = self.check_supported(target)
37+
if not target_info:
38+
raise TargetNotSupportedException("Target not supported in CMSIS pack")
5139

5240
self.url = target_info['pdsc_file']
5341
self.pack_url, self.pack_id = ntpath.split(self.url)
54-
self.dname = cpu_name
42+
self.dname = target_info["_cpu_name"]
43+
self.core = target_info["_core"]
5544
self.dfpu = target_info['processor']['fpu']
5645
self.debug, self.dvendor = self.vendor_debug(target_info['vendor'])
5746
self.dendian = target_info['processor'].get('endianness','Little-endian')
5847
self.debug_svd = target_info.get('debug', '')
5948
self.compile_header = target_info['compile']['header']
49+
self.target_info = target_info
6050

61-
def check_version(self, filename):
62-
with open(filename) as data_file:
63-
data = json.load(data_file)
64-
return data.get("version", "0") == "0.1.0"
51+
@staticmethod
52+
def check_supported(target):
53+
t = TARGET_MAP[target]
54+
try:
55+
cpu_name = t.device_name
56+
target_info = DeviceCMSIS.CACHE.index[cpu_name]
57+
# Target does not have device name or pdsc file
58+
except:
59+
try:
60+
# Try to find the core as a generic CMSIS target
61+
cpu_name = DeviceCMSIS.cpu_cmsis(t.core)
62+
target_info = DeviceCMSIS.index[cpu_name]
63+
except:
64+
return False
65+
target_info["_cpu_name"] = cpu_name
66+
target_info["_core"] = t.core
67+
return target_info
6568

6669
def vendor_debug(self, vendor):
6770
reg = "([\w\s]+):?\d*?"
@@ -74,9 +77,9 @@ def vendor_debug(self, vendor):
7477
}
7578
return debug_map.get(vendor_match, "CMSIS-DAP"), vendor_match
7679

77-
def cpu_cmsis(self):
80+
@staticmethod
81+
def cpu_cmsis(cpu):
7882
#Cortex-M4F => ARMCM4_FP, Cortex-M0+ => ARMCM0P
79-
cpu = self.core
8083
cpu = cpu.replace("Cortex-","ARMC")
8184
cpu = cpu.replace("+","P")
8285
cpu = cpu.replace("F","_FP")

tools/export/exporters.py

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,6 @@
1111
from tools.targets import TARGET_MAP
1212

1313

14-
class OldLibrariesException(Exception):
15-
"""Exception that indicates an export can not complete due to an out of date
16-
library version.
17-
"""
18-
pass
19-
20-
class FailedBuildException(Exception):
21-
"""Exception that indicates that a build failed"""
22-
pass
23-
2414
class TargetNotSupportedException(Exception):
2515
"""Indicates that an IDE does not support a particular MCU"""
2616
pass
@@ -119,13 +109,6 @@ def get_source_paths(self):
119109
source_files.extend(getattr(self.resources, key))
120110
return list(set([os.path.dirname(src) for src in source_files]))
121111

122-
def check_supported(self):
123-
"""Indicated if this combination of IDE and MCU is supported"""
124-
if self.target not in self.TARGETS or \
125-
self.TOOLCHAIN not in TARGET_MAP[self.target].supported_toolchains:
126-
raise TargetNotSupportedException()
127-
return True
128-
129112
def gen_file(self, template_file, data, target_file):
130113
"""Generates a project file from a template using jinja"""
131114
jinja_loader = FileSystemLoader(
@@ -153,9 +136,31 @@ def make_key(self, src):
153136
def group_project_files(self, sources):
154137
"""Group the source files by their encompassing directory
155138
Positional Arguments:
156-
sources - array of sourc locations
139+
sources - array of source locations
157140
158141
Returns a dictionary of {group name: list of source locations}
159142
"""
160143
data = sorted(sources, key=self.make_key)
161144
return {k: list(g) for k,g in groupby(data, self.make_key)}
145+
146+
@staticmethod
147+
def build(project_name, log_name='build_log.txt', cleanup=True):
148+
"""Invoke exporters build command within a subprocess.
149+
This method is assumed to be executed at the same level as exporter
150+
project files and project source code.
151+
See uvision/__init__.py, iar/__init__.py, and makefile/__init__.py for
152+
example implemenation.
153+
154+
Positional Arguments:
155+
project_name - the name of the project to build; often required by
156+
exporter's build command.
157+
158+
Keyword Args:
159+
log_name - name of the build log to create. Written and printed out,
160+
deleted if cleanup = True
161+
cleanup - a boolean dictating whether exported project files and
162+
build log are removed after build
163+
164+
Returns -1 on failure and 0 on success
165+
"""
166+
raise NotImplemented("Implement in derived Exporter class.")

tools/export/iar/__init__.py

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
from os.path import sep, join, exists
33
from collections import namedtuple
44
from subprocess import Popen, PIPE
5-
from distutils.spawn import find_executable
5+
import shutil
66
import re
77
import sys
88

99
from tools.targets import TARGET_MAP
10-
from tools.export.exporters import Exporter, FailedBuildException
10+
from tools.export.exporters import Exporter
1111
import json
1212
from tools.export.cmsis import DeviceCMSIS
1313
from multiprocessing import cpu_count
@@ -29,7 +29,8 @@ class IAR(Exporter):
2929
#iar_definitions.json
3030
TARGETS = [target for target, obj in TARGET_MAP.iteritems()
3131
if hasattr(obj, 'device_name') and
32-
obj.device_name in IAR_DEFS.keys()]
32+
obj.device_name in IAR_DEFS.keys() and "IAR" in obj.supported_toolchains
33+
and DeviceCMSIS.check_supported(target)]
3334

3435
SPECIAL_TEMPLATES = {
3536
'rz_a1h' : 'iar/iar_rz_a1h.ewp.tmpl',
@@ -120,22 +121,13 @@ def generate(self):
120121
self.gen_file('iar/ewd.tmpl', ctx, self.project_name + ".ewd")
121122
self.gen_file(self.get_ewp_template(), ctx, self.project_name + ".ewp")
122123

123-
def build(self):
124+
@staticmethod
125+
def build(project_name, cleanup=True):
124126
""" Build IAR project """
125127
# > IarBuild [project_path] -build [project_name]
126-
proj_file = join(self.export_dir, self.project_name + ".ewp")
127128

128-
if find_executable("IarBuild"):
129-
iar_exe = "IarBuild.exe"
130-
else:
131-
iar_exe = join('C:', sep,
132-
'Program Files (x86)', 'IAR Systems',
133-
'Embedded Workbench 7.5', 'common', 'bin',
134-
'IarBuild.exe')
135-
if not exists(iar_exe):
136-
raise Exception("IarBuild.exe not found. Add to path.")
137-
138-
cmd = [iar_exe, proj_file, '-build', self.project_name]
129+
proj_file = project_name + ".ewp"
130+
cmd = ["IarBuild.exe", proj_file, '-build', project_name]
139131

140132
# IAR does not support a '0' option to automatically use all
141133
# available CPUs, so we use Python's multiprocessing library
@@ -156,7 +148,14 @@ def build(self):
156148
m = re.match(error_re, line)
157149
if m is not None:
158150
num_errors = int(m.group(1))
151+
152+
if cleanup:
153+
os.remove(project_name + ".ewp")
154+
os.remove(project_name + ".ewd")
155+
os.remove(project_name + ".eww")
156+
shutil.rmtree('.build')
157+
159158
if num_errors !=0:
160159
# Seems like something went wrong.
161-
raise FailedBuildException("Project: %s build failed with %s erros" % (
162-
proj_file, num_errors))
160+
return -1
161+
return 0

tools/export/makefile/__init__.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616
"""
1717
from os.path import splitext, basename, relpath, join, abspath, dirname,\
1818
exists
19-
from os import curdir, getcwd
19+
from os import remove
20+
import sys
21+
from subprocess import check_output, CalledProcessError, Popen, PIPE
22+
import shutil
2023
from jinja2.exceptions import TemplateNotFound
2124
from tools.export.exporters import Exporter
2225
from tools.utils import NotSupportedException
@@ -102,6 +105,38 @@ def generate(self):
102105
else:
103106
raise NotSupportedException("This make tool is in development")
104107

108+
@staticmethod
109+
def build(project_name, log_name="build_log.txt", cleanup=True):
110+
""" Build Make project """
111+
# > Make -j
112+
cmd = ["make", "-j"]
113+
p = Popen(cmd, stdout=PIPE, stderr=PIPE)
114+
ret = p.communicate()
115+
out, err = ret[0], ret[1]
116+
ret_code = p.returncode
117+
with open(log_name, 'w+') as f:
118+
f.write("=" * 10 + "OUT" + "=" * 10 + "\n")
119+
f.write(out)
120+
f.write("=" * 10 + "ERR" + "=" * 10 + "\n")
121+
f.write(err)
122+
if ret_code == 0:
123+
f.write("SUCCESS")
124+
else:
125+
f.write("FAILURE")
126+
with open(log_name, 'r') as f:
127+
print "\n".join(f.readlines())
128+
sys.stdout.flush()
129+
130+
if cleanup:
131+
remove("Makefile")
132+
remove(log_name)
133+
if exists('.build'):
134+
shutil.rmtree('.build')
135+
if ret_code != 0:
136+
# Seems like something went wrong.
137+
return -1
138+
return 0
139+
105140

106141
class GccArm(Makefile):
107142
"""GCC ARM specific makefile target"""

tools/export/uvision/__init__.py

Lines changed: 24 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
import ntpath
44
import copy
55
from collections import namedtuple
6-
from distutils.spawn import find_executable
6+
import shutil
77
import subprocess
88
import re
99

1010
from tools.arm_pack_manager import Cache
1111
from tools.targets import TARGET_MAP
12-
from tools.export.exporters import Exporter, FailedBuildException
12+
from tools.export.exporters import Exporter
1313
from tools.export.cmsis import DeviceCMSIS
1414

1515
cache_d = False
@@ -117,10 +117,15 @@ class Uvision(Exporter):
117117
project file (.uvprojx).
118118
The needed information can be viewed in uvision.tmpl
119119
"""
120-
NAME = 'cmsis'
120+
NAME = 'uvision5'
121121
TOOLCHAIN = 'ARM'
122-
TARGETS = [target for target, obj in TARGET_MAP.iteritems()
123-
if "ARM" in obj.supported_toolchains]
122+
TARGETS = []
123+
for target, obj in TARGET_MAP.iteritems():
124+
if not ("ARM" in obj.supported_toolchains and hasattr(obj, "device_name")):
125+
continue
126+
if not DeviceCMSIS.check_supported(target):
127+
continue
128+
TARGETS.append(target)
124129
#File associations within .uvprojx file
125130
file_types = {'.cpp': 8, '.c': 1, '.s': 2,
126131
'.obj': 3, '.o': 3, '.lib': 4,
@@ -200,35 +205,24 @@ def generate(self):
200205
self.gen_file('uvision/uvision.tmpl', ctx, self.project_name+".uvprojx")
201206
self.gen_file('uvision/uvision_debug.tmpl', ctx, self.project_name + ".uvoptx")
202207

203-
def build(self):
204-
ERRORLEVEL = {
205-
0: 'success (0 warnings, 0 errors)',
206-
1: 'warnings',
207-
2: 'errors',
208-
3: 'fatal errors',
209-
11: 'cant write to project file',
210-
12: 'device error',
211-
13: 'error writing',
212-
15: 'error reading xml file',
213-
}
208+
@staticmethod
209+
def build(project_name, log_name='build_log.txt', cleanup=True):
210+
""" Build Uvision project """
211+
# > UV4.exe -r -j0 -o [log_name] [project_name].uvprojx
214212
success = 0
215213
warn = 1
216-
if find_executable("UV4"):
217-
uv_exe = "UV4.exe"
218-
else:
219-
uv_exe = join('C:', sep,
220-
'Keil_v5', 'UV4', 'UV4.exe')
221-
if not exists(uv_exe):
222-
raise Exception("UV4.exe not found. Add to path.")
223-
cmd = [uv_exe, '-r', '-j0', '-o', join(self.export_dir,'build_log.txt'), join(self.export_dir,self.project_name+".uvprojx")]
214+
cmd = ["UV4.exe", '-r', '-j0', '-o', log_name, project_name+".uvprojx"]
224215
ret_code = subprocess.call(cmd)
225-
with open(join(self.export_dir, 'build_log.txt'), 'r') as build_log:
216+
with open(log_name, 'r') as build_log:
226217
print build_log.read()
218+
if cleanup:
219+
os.remove(log_name)
220+
os.remove(project_name+".uvprojx")
221+
os.remove(project_name+".uvoptx")
222+
shutil.rmtree(".build")
223+
227224

228225
if ret_code != success and ret_code != warn:
229226
# Seems like something went wrong.
230-
raise FailedBuildException("Project: %s build failed with the status: %s" % (
231-
self.project_name, ERRORLEVEL.get(ret_code, "Unknown")))
232-
else:
233-
return "Project: %s build succeeded with the status: %s" % (
234-
self.project_name, ERRORLEVEL.get(ret_code, "Unknown"))
227+
return -1
228+
return 0

tools/project.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,9 @@ def main():
233233
if (options.program is None) and (not options.source_dir):
234234
args_error(parser, "one of -p, -n, or --source is required")
235235
# Export to selected toolchain
236-
_, toolchain_name = get_exporter_toolchain(options.ide)
236+
exporter, toolchain_name = get_exporter_toolchain(options.ide)
237+
if options.mcu not in exporter.TARGETS:
238+
args_error(parser, "%s not supported by %s"%(options.mcu,options.ide))
237239
profile = extract_profile(parser, options, toolchain_name)
238240
export(options.mcu, options.ide, build=options.build,
239241
src=options.source_dir, macros=options.macros,

tools/project_api.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ def generate_project_files(resources, export_path, target, name, toolchain, ide,
8686
exporter_cls, _ = get_exporter_toolchain(ide)
8787
exporter = exporter_cls(target, export_path, name, toolchain,
8888
extra_symbols=macros, resources=resources)
89-
exporter.check_supported()
9089
exporter.generate()
9190
files = exporter.generated_files
9291
return files, exporter

0 commit comments

Comments
 (0)