]> git.friedersdorff.com Git - max/tmk_keyboard.git/blob - tmk_core/tool/mbed/mbed-sdk/workspace_tools/build_api.py
xt_usb: Fix XT soft reset
[max/tmk_keyboard.git] / tmk_core / tool / mbed / mbed-sdk / workspace_tools / build_api.py
1 """
2 mbed SDK
3 Copyright (c) 2011-2013 ARM Limited
4
5 Licensed under the Apache License, Version 2.0 (the "License");
6 you may not use this file except in compliance with the License.
7 You may obtain a copy of the License at
8
9     http://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16 """
17
18 import re
19 import tempfile
20 import colorama
21
22
23 from types import ListType
24 from shutil import rmtree
25 from os.path import join, exists, basename
26
27 from workspace_tools.utils import mkdir, run_cmd, run_cmd_ext
28 from workspace_tools.paths import MBED_TARGETS_PATH, MBED_LIBRARIES, MBED_API, MBED_HAL, MBED_COMMON
29 from workspace_tools.targets import TARGET_NAMES, TARGET_MAP
30 from workspace_tools.libraries import Library
31 from workspace_tools.toolchains import TOOLCHAIN_CLASSES
32 from jinja2 import FileSystemLoader
33 from jinja2.environment import Environment
34
35
36 def build_project(src_path, build_path, target, toolchain_name,
37         libraries_paths=None, options=None, linker_script=None,
38         clean=False, notify=None, verbose=False, name=None, macros=None, inc_dirs=None, jobs=1, silent=False):
39     """ This function builds project. Project can be for example one test / UT
40     """
41     # Toolchain instance
42     toolchain = TOOLCHAIN_CLASSES[toolchain_name](target, options, notify, macros, silent)
43     toolchain.VERBOSE = verbose
44     toolchain.jobs = jobs
45     toolchain.build_all = clean
46     src_paths = [src_path] if type(src_path) != ListType else src_path
47
48     # We need to remove all paths which are repeated to avoid
49     # multiple compilations and linking with the same objects
50     src_paths = [src_paths[0]] + list(set(src_paths[1:]))
51
52     PROJECT_BASENAME = basename(src_paths[0])
53
54     if name is None:
55         # We will use default project name based on project folder name
56         name = PROJECT_BASENAME
57         toolchain.info("Building project %s (%s, %s)" % (PROJECT_BASENAME.upper(), target.name, toolchain_name))
58     else:
59         # User used custom global project name to have the same name for the
60         toolchain.info("Building project %s to %s (%s, %s)" % (PROJECT_BASENAME.upper(), name, target.name, toolchain_name))
61
62     # Scan src_path and libraries_paths for resources
63     resources = toolchain.scan_resources(src_paths[0])
64     for path in src_paths[1:]:
65         resources.add(toolchain.scan_resources(path))
66     if libraries_paths is not None:
67         src_paths.extend(libraries_paths)
68         for path in libraries_paths:
69             resources.add(toolchain.scan_resources(path))
70
71     if linker_script is not None:
72         resources.linker_script = linker_script
73
74     # Build Directory
75     if clean:
76         if exists(build_path):
77             rmtree(build_path)
78     mkdir(build_path)
79
80     # We need to add if necessary additional include directories
81     if inc_dirs:
82         if type(inc_dirs) == ListType:
83             resources.inc_dirs.extend(inc_dirs)
84         else:
85             resources.inc_dirs.append(inc_dirs)
86
87     # Compile Sources
88     for path in src_paths:
89         src = toolchain.scan_resources(path)
90         objects = toolchain.compile_sources(src, build_path, resources.inc_dirs)
91         resources.objects.extend(objects)
92
93     # Link Program
94     return toolchain.link_program(resources, build_path, name)
95
96
97 def build_library(src_paths, build_path, target, toolchain_name,
98          dependencies_paths=None, options=None, name=None, clean=False,
99          notify=None, verbose=False, macros=None, inc_dirs=None, inc_dirs_ext=None, jobs=1, silent=False):
100     """ src_path: the path of the source directory
101     build_path: the path of the build directory
102     target: ['LPC1768', 'LPC11U24', 'LPC2368']
103     toolchain: ['ARM', 'uARM', 'GCC_ARM', 'GCC_CS', 'GCC_CR']
104     library_paths: List of paths to additional libraries
105     clean: Rebuild everything if True
106     notify: Notify function for logs
107     verbose: Write the actual tools command lines if True
108     inc_dirs: additional include directories which should be included in build
109     inc_dirs_ext: additional include directories which should be copied to library directory
110     """
111     if type(src_paths) != ListType:
112         src_paths = [src_paths]
113
114     for src_path in src_paths:
115         if not exists(src_path):
116             raise Exception("The library source folder does not exist: %s", src_path)
117
118     # Toolchain instance
119     toolchain = TOOLCHAIN_CLASSES[toolchain_name](target, options, macros=macros, notify=notify, silent=silent)
120     toolchain.VERBOSE = verbose
121     toolchain.jobs = jobs
122     toolchain.build_all = clean
123
124     # The first path will give the name to the library
125     name = basename(src_paths[0])
126     toolchain.info("Building library %s (%s, %s)" % (name.upper(), target.name, toolchain_name))
127
128     # Scan Resources
129     resources = []
130     for src_path in src_paths:
131         resources.append(toolchain.scan_resources(src_path))
132
133     # Add extra include directories / files which are required by library
134     # This files usually are not in the same directory as source files so
135     # previous scan will not include them
136     if inc_dirs_ext is not None:
137         for inc_ext in inc_dirs_ext:
138             resources.append(toolchain.scan_resources(inc_ext))
139
140     # Dependencies Include Paths
141     dependencies_include_dir = []
142     if dependencies_paths is not None:
143         for path in dependencies_paths:
144             lib_resources = toolchain.scan_resources(path)
145             dependencies_include_dir.extend(lib_resources.inc_dirs)
146
147     if inc_dirs:
148         dependencies_include_dir.extend(inc_dirs)
149
150     # Create the desired build directory structure
151     bin_path = join(build_path, toolchain.obj_path)
152     mkdir(bin_path)
153     tmp_path = join(build_path, '.temp', toolchain.obj_path)
154     mkdir(tmp_path)
155
156     # Copy Headers
157     for resource in resources:
158         toolchain.copy_files(resource.headers, build_path, rel_path=resource.base_path)
159     dependencies_include_dir.extend(toolchain.scan_resources(build_path).inc_dirs)
160
161     # Compile Sources
162     objects = []
163     for resource in resources:
164         objects.extend(toolchain.compile_sources(resource, tmp_path, dependencies_include_dir))
165
166     toolchain.build_library(objects, bin_path, name)
167
168
169 def build_lib(lib_id, target, toolchain, options=None, verbose=False, clean=False, macros=None, notify=None, jobs=1, silent=False):
170     """ Wrapper for build_library function.
171         Function builds library in proper directory using all dependencies and macros defined by user.
172     """
173     lib = Library(lib_id)
174     if lib.is_supported(target, toolchain):
175         # We need to combine macros from parameter list with macros from library definition
176         MACROS = lib.macros if lib.macros else []
177         if macros:
178             MACROS.extend(macros)
179
180         build_library(lib.source_dir, lib.build_dir, target, toolchain, lib.dependencies, options,
181                       verbose=verbose,
182                       silent=silent,
183                       clean=clean,
184                       macros=MACROS,
185                       notify=notify,
186                       inc_dirs=lib.inc_dirs,
187                       inc_dirs_ext=lib.inc_dirs_ext,
188                       jobs=jobs)
189     else:
190         print 'Library "%s" is not yet supported on target %s with toolchain %s' % (lib_id, target.name, toolchain)
191
192
193 # We do have unique legacy conventions about how we build and package the mbed library
194 def build_mbed_libs(target, toolchain_name, options=None, verbose=False, clean=False, macros=None, notify=None, jobs=1, silent=False):
195     """ Function returns True is library was built and false if building was skipped """
196     # Check toolchain support
197     if toolchain_name not in target.supported_toolchains:
198         supported_toolchains_text = ", ".join(target.supported_toolchains)
199         print '%s target is not yet supported by toolchain %s' % (target.name, toolchain_name)
200         print '%s target supports %s toolchain%s' % (target.name, supported_toolchains_text, 's' if len(target.supported_toolchains) > 1 else '')
201         return False
202
203     # Toolchain
204     toolchain = TOOLCHAIN_CLASSES[toolchain_name](target, options, macros=macros, notify=notify, silent=silent)
205     toolchain.VERBOSE = verbose
206     toolchain.jobs = jobs
207     toolchain.build_all = clean
208
209     # Source and Build Paths
210     BUILD_TARGET = join(MBED_LIBRARIES, "TARGET_" + target.name)
211     BUILD_TOOLCHAIN = join(BUILD_TARGET, "TOOLCHAIN_" + toolchain.name)
212     mkdir(BUILD_TOOLCHAIN)
213
214     TMP_PATH = join(MBED_LIBRARIES, '.temp', toolchain.obj_path)
215     mkdir(TMP_PATH)
216
217     # CMSIS
218     toolchain.info("Building library %s (%s, %s)"% ('CMSIS', target.name, toolchain_name))
219     cmsis_src = join(MBED_TARGETS_PATH, "cmsis")
220     resources = toolchain.scan_resources(cmsis_src)
221
222     toolchain.copy_files(resources.headers, BUILD_TARGET)
223     toolchain.copy_files(resources.linker_script, BUILD_TOOLCHAIN)
224     toolchain.copy_files(resources.bin_files, BUILD_TOOLCHAIN)
225
226     objects = toolchain.compile_sources(resources, TMP_PATH)
227     toolchain.copy_files(objects, BUILD_TOOLCHAIN)
228
229     # mbed
230     toolchain.info("Building library %s (%s, %s)" % ('MBED', target.name, toolchain_name))
231
232     # Common Headers
233     toolchain.copy_files(toolchain.scan_resources(MBED_API).headers, MBED_LIBRARIES)
234     toolchain.copy_files(toolchain.scan_resources(MBED_HAL).headers, MBED_LIBRARIES)
235
236     # Target specific sources
237     HAL_SRC = join(MBED_TARGETS_PATH, "hal")
238     hal_implementation = toolchain.scan_resources(HAL_SRC)
239     toolchain.copy_files(hal_implementation.headers + hal_implementation.hex_files + hal_implementation.libraries, BUILD_TARGET, HAL_SRC)
240     incdirs = toolchain.scan_resources(BUILD_TARGET).inc_dirs
241     objects = toolchain.compile_sources(hal_implementation, TMP_PATH, [MBED_LIBRARIES] + incdirs)
242
243     # Common Sources
244     mbed_resources = toolchain.scan_resources(MBED_COMMON)
245     objects += toolchain.compile_sources(mbed_resources, TMP_PATH, [MBED_LIBRARIES] + incdirs)
246
247     # A number of compiled files need to be copied as objects as opposed to
248     # being part of the mbed library, for reasons that have to do with the way
249     # the linker search for symbols in archives. These are:
250     #   - retarget.o: to make sure that the C standard lib symbols get overridden
251     #   - board.o: mbed_die is weak
252     #   - mbed_overrides.o: this contains platform overrides of various weak SDK functions
253     separate_names, separate_objects = ['retarget.o', 'board.o', 'mbed_overrides.o'], []
254     for o in objects:
255         for name in separate_names:
256             if o.endswith(name):
257                 separate_objects.append(o)
258     for o in separate_objects:
259         objects.remove(o)
260     toolchain.build_library(objects, BUILD_TOOLCHAIN, "mbed")
261     for o in separate_objects:
262         toolchain.copy_files(o, BUILD_TOOLCHAIN)
263     return True
264
265
266 def get_unique_supported_toolchains():
267     """ Get list of all unique toolchains supported by targets """
268     unique_supported_toolchains = []
269     for target in TARGET_NAMES:
270         for toolchain in TARGET_MAP[target].supported_toolchains:
271             if toolchain not in unique_supported_toolchains:
272                 unique_supported_toolchains.append(toolchain)
273     return unique_supported_toolchains
274
275
276 def mcu_toolchain_matrix(verbose_html=False, platform_filter=None):
277     """  Shows target map using prettytable """
278     unique_supported_toolchains = get_unique_supported_toolchains()
279     from prettytable import PrettyTable # Only use it in this function so building works without extra modules
280
281     # All tests status table print
282     columns = ["Platform"] + unique_supported_toolchains
283     pt = PrettyTable(["Platform"] + unique_supported_toolchains)
284     # Align table
285     for col in columns:
286         pt.align[col] = "c"
287     pt.align["Platform"] = "l"
288
289     perm_counter = 0
290     target_counter = 0
291     for target in sorted(TARGET_NAMES):
292         if platform_filter is not None:
293             # FIlter out platforms using regex
294             if re.search(platform_filter, target) is None:
295                 continue
296         target_counter += 1
297
298         row = [target]  # First column is platform name
299         default_toolchain = TARGET_MAP[target].default_toolchain
300         for unique_toolchain in unique_supported_toolchains:
301             text = "-"
302             if default_toolchain == unique_toolchain:
303                 text = "Default"
304                 perm_counter += 1
305             elif unique_toolchain in TARGET_MAP[target].supported_toolchains:
306                 text = "Supported"
307                 perm_counter += 1
308             row.append(text)
309         pt.add_row(row)
310
311     result = pt.get_html_string() if verbose_html else pt.get_string()
312     result += "\n"
313     result += "*Default - default on-line compiler\n"
314     result += "*Supported - supported off-line compiler\n"
315     result += "\n"
316     result += "Total platforms: %d\n"% (target_counter)
317     result += "Total permutations: %d"% (perm_counter)
318     return result
319
320
321 def get_target_supported_toolchains(target):
322     """ Returns target supported toolchains list """
323     return TARGET_MAP[target].supported_toolchains if target in TARGET_MAP else None
324
325
326 def static_analysis_scan(target, toolchain_name, CPPCHECK_CMD, CPPCHECK_MSG_FORMAT, options=None, verbose=False, clean=False, macros=None, notify=None, jobs=1):
327     # Toolchain
328     toolchain = TOOLCHAIN_CLASSES[toolchain_name](target, options, macros=macros, notify=notify)
329     toolchain.VERBOSE = verbose
330     toolchain.jobs = jobs
331     toolchain.build_all = clean
332
333     # Source and Build Paths
334     BUILD_TARGET = join(MBED_LIBRARIES, "TARGET_" + target.name)
335     BUILD_TOOLCHAIN = join(BUILD_TARGET, "TOOLCHAIN_" + toolchain.name)
336     mkdir(BUILD_TOOLCHAIN)
337
338     TMP_PATH = join(MBED_LIBRARIES, '.temp', toolchain.obj_path)
339     mkdir(TMP_PATH)
340
341     # CMSIS
342     toolchain.info("Static analysis for %s (%s, %s)" % ('CMSIS', target.name, toolchain_name))
343     cmsis_src = join(MBED_TARGETS_PATH, "cmsis")
344     resources = toolchain.scan_resources(cmsis_src)
345
346     # Copy files before analysis
347     toolchain.copy_files(resources.headers, BUILD_TARGET)
348     toolchain.copy_files(resources.linker_script, BUILD_TOOLCHAIN)
349
350     # Gather include paths, c, cpp sources and macros to transfer to cppcheck command line
351     includes = ["-I%s"% i for i in resources.inc_dirs]
352     includes.append("-I%s"% str(BUILD_TARGET))
353     c_sources = " ".join(resources.c_sources)
354     cpp_sources = " ".join(resources.cpp_sources)
355     macros = ["-D%s"% s for s in toolchain.get_symbols() + toolchain.macros]
356
357     includes = map(str.strip, includes)
358     macros = map(str.strip, macros)
359
360     check_cmd = CPPCHECK_CMD
361     check_cmd += CPPCHECK_MSG_FORMAT
362     check_cmd += includes
363     check_cmd += macros
364
365     # We need to pass some params via file to avoid "command line too long in some OSs"
366     tmp_file = tempfile.NamedTemporaryFile(delete=False)
367     tmp_file.writelines(line + '\n' for line in c_sources.split())
368     tmp_file.writelines(line + '\n' for line in cpp_sources.split())
369     tmp_file.close()
370     check_cmd += ["--file-list=%s"% tmp_file.name]
371
372     _stdout, _stderr, _rc = run_cmd(check_cmd)
373     if verbose:
374         print _stdout
375     print _stderr
376
377     # =========================================================================
378
379     # MBED
380     toolchain.info("Static analysis for %s (%s, %s)" % ('MBED', target.name, toolchain_name))
381
382     # Common Headers
383     toolchain.copy_files(toolchain.scan_resources(MBED_API).headers, MBED_LIBRARIES)
384     toolchain.copy_files(toolchain.scan_resources(MBED_HAL).headers, MBED_LIBRARIES)
385
386     # Target specific sources
387     HAL_SRC = join(MBED_TARGETS_PATH, "hal")
388     hal_implementation = toolchain.scan_resources(HAL_SRC)
389
390     # Copy files before analysis
391     toolchain.copy_files(hal_implementation.headers + hal_implementation.hex_files, BUILD_TARGET, HAL_SRC)
392     incdirs = toolchain.scan_resources(BUILD_TARGET)
393
394     target_includes = ["-I%s" % i for i in incdirs.inc_dirs]
395     target_includes.append("-I%s"% str(BUILD_TARGET))
396     target_includes.append("-I%s"% str(HAL_SRC))
397     target_c_sources = " ".join(incdirs.c_sources)
398     target_cpp_sources = " ".join(incdirs.cpp_sources)
399     target_macros = ["-D%s"% s for s in toolchain.get_symbols() + toolchain.macros]
400
401     # Common Sources
402     mbed_resources = toolchain.scan_resources(MBED_COMMON)
403
404     # Gather include paths, c, cpp sources and macros to transfer to cppcheck command line
405     mbed_includes = ["-I%s" % i for i in mbed_resources.inc_dirs]
406     mbed_includes.append("-I%s"% str(BUILD_TARGET))
407     mbed_includes.append("-I%s"% str(MBED_COMMON))
408     mbed_includes.append("-I%s"% str(MBED_API))
409     mbed_includes.append("-I%s"% str(MBED_HAL))
410     mbed_c_sources = " ".join(mbed_resources.c_sources)
411     mbed_cpp_sources = " ".join(mbed_resources.cpp_sources)
412
413     target_includes = map(str.strip, target_includes)
414     mbed_includes = map(str.strip, mbed_includes)
415     target_macros = map(str.strip, target_macros)
416
417     check_cmd = CPPCHECK_CMD
418     check_cmd += CPPCHECK_MSG_FORMAT
419     check_cmd += target_includes
420     check_cmd += mbed_includes
421     check_cmd += target_macros
422
423     # We need to pass some parames via file to avoid "command line too long in some OSs"
424     tmp_file = tempfile.NamedTemporaryFile(delete=False)
425     tmp_file.writelines(line + '\n' for line in target_c_sources.split())
426     tmp_file.writelines(line + '\n' for line in target_cpp_sources.split())
427     tmp_file.writelines(line + '\n' for line in mbed_c_sources.split())
428     tmp_file.writelines(line + '\n' for line in mbed_cpp_sources.split())
429     tmp_file.close()
430     check_cmd += ["--file-list=%s"% tmp_file.name]
431
432     _stdout, _stderr, _rc = run_cmd_ext(check_cmd)
433     if verbose:
434         print _stdout
435     print _stderr
436
437
438 def static_analysis_scan_lib(lib_id, target, toolchain, cppcheck_cmd, cppcheck_msg_format,
439                              options=None, verbose=False, clean=False, macros=None, notify=None, jobs=1):
440     lib = Library(lib_id)
441     if lib.is_supported(target, toolchain):
442         static_analysis_scan_library(lib.source_dir, lib.build_dir, target, toolchain, cppcheck_cmd, cppcheck_msg_format,
443                       lib.dependencies, options,
444                       verbose=verbose, clean=clean, macros=macros, notify=notify, jobs=jobs)
445     else:
446         print 'Library "%s" is not yet supported on target %s with toolchain %s'% (lib_id, target.name, toolchain)
447
448
449 def static_analysis_scan_library(src_paths, build_path, target, toolchain_name, cppcheck_cmd, cppcheck_msg_format,
450          dependencies_paths=None, options=None, name=None, clean=False,
451          notify=None, verbose=False, macros=None, jobs=1):
452     """ Function scans library (or just some set of sources/headers) for staticly detectable defects """
453     if type(src_paths) != ListType:
454         src_paths = [src_paths]
455
456     for src_path in src_paths:
457         if not exists(src_path):
458             raise Exception("The library source folder does not exist: %s", src_path)
459
460     # Toolchain instance
461     toolchain = TOOLCHAIN_CLASSES[toolchain_name](target, options, macros=macros, notify=notify)
462     toolchain.VERBOSE = verbose
463     toolchain.jobs = jobs
464
465     # The first path will give the name to the library
466     name = basename(src_paths[0])
467     toolchain.info("Static analysis for library %s (%s, %s)" % (name.upper(), target.name, toolchain_name))
468
469     # Scan Resources
470     resources = []
471     for src_path in src_paths:
472         resources.append(toolchain.scan_resources(src_path))
473
474     # Dependencies Include Paths
475     dependencies_include_dir = []
476     if dependencies_paths is not None:
477         for path in dependencies_paths:
478             lib_resources = toolchain.scan_resources(path)
479             dependencies_include_dir.extend(lib_resources.inc_dirs)
480
481     # Create the desired build directory structure
482     bin_path = join(build_path, toolchain.obj_path)
483     mkdir(bin_path)
484     tmp_path = join(build_path, '.temp', toolchain.obj_path)
485     mkdir(tmp_path)
486
487     # Gather include paths, c, cpp sources and macros to transfer to cppcheck command line
488     includes = ["-I%s" % i for i in dependencies_include_dir + src_paths]
489     c_sources = " "
490     cpp_sources = " "
491     macros = ['-D%s' % s for s in toolchain.get_symbols() + toolchain.macros]
492
493     # Copy Headers
494     for resource in resources:
495         toolchain.copy_files(resource.headers, build_path, rel_path=resource.base_path)
496         includes += ["-I%s" % i for i in resource.inc_dirs]
497         c_sources += " ".join(resource.c_sources) + " "
498         cpp_sources += " ".join(resource.cpp_sources) + " "
499
500     dependencies_include_dir.extend(toolchain.scan_resources(build_path).inc_dirs)
501
502     includes = map(str.strip, includes)
503     macros = map(str.strip, macros)
504
505     check_cmd = cppcheck_cmd
506     check_cmd += cppcheck_msg_format
507     check_cmd += includes
508     check_cmd += macros
509
510     # We need to pass some parameters via file to avoid "command line too long in some OSs"
511     # Temporary file is created to store e.g. cppcheck list of files for command line
512     tmp_file = tempfile.NamedTemporaryFile(delete=False)
513     tmp_file.writelines(line + '\n' for line in c_sources.split())
514     tmp_file.writelines(line + '\n' for line in cpp_sources.split())
515     tmp_file.close()
516     check_cmd += ["--file-list=%s"% tmp_file.name]
517
518     # This will allow us to grab result from both stdio and stderr outputs (so we can show them)
519     # We assume static code analysis tool is outputting defects on STDERR
520     _stdout, _stderr, _rc = run_cmd_ext(check_cmd)
521     if verbose:
522         print _stdout
523     print _stderr
524
525
526 def print_build_results(result_list, build_name):
527     """ Generate result string for build results """
528     result = ""
529     if result_list:
530         result += build_name + "\n"
531         result += "\n".join(["  * %s" % f for f in result_list])
532         result += "\n"
533     return result
534
535 def write_build_report(build_report, template_filename, filename):
536     build_report_failing = []
537     build_report_passing = []
538
539     for report in build_report:
540         if len(report["failing"]) > 0:
541             build_report_failing.append(report)
542         else:
543             build_report_passing.append(report)
544
545     env = Environment(extensions=['jinja2.ext.with_'])
546     env.loader = FileSystemLoader('ci_templates')
547     template = env.get_template(template_filename)
548
549     with open(filename, 'w+') as f:
550         f.write(template.render(failing_builds=build_report_failing, passing_builds=build_report_passing))