Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Fork of mbed-sdk-tools by
Diff: project_api.py
- Revision:
- 32:8ea194f6145b
- Parent:
- 26:ed5e0d4e691e
diff -r f12ce67666d0 -r 8ea194f6145b project_api.py --- a/project_api.py Mon Aug 29 11:56:59 2016 +0100 +++ b/project_api.py Wed Jan 04 11:58:24 2017 -0600 @@ -1,110 +1,264 @@ +""" The new way of doing exports """ import sys -from os.path import join, abspath, dirname, exists, basename +from os.path import join, abspath, dirname, exists +from os.path import basename, relpath, normpath, splitext +from os import makedirs, walk ROOT = abspath(join(dirname(__file__), "..")) sys.path.insert(0, ROOT) +import copy +from shutil import rmtree +import zipfile -from tools.paths import EXPORT_WORKSPACE, EXPORT_TMP -from tools.paths import MBED_BASE, MBED_LIBRARIES -from tools.export import export, setup_user_prj -from tools.utils import mkdir -from tools.tests import Test, TEST_MAP, TESTS -from tools.libraries import LIBRARIES +from tools.build_api import prepare_toolchain +from tools.build_api import scan_resources +from tools.export import EXPORTERS +from tools.toolchains import Resources + + +def get_exporter_toolchain(ide): + """ Return the exporter class and the toolchain string as a tuple -try: - import tools.private_settings as ps -except: - ps = object() + Positional arguments: + ide - the ide name of an exporter + """ + return EXPORTERS[ide], EXPORTERS[ide].TOOLCHAIN + + +def rewrite_basepath(file_name, resources, export_path, loc): + """ Replace the basepath of filename with export_path + + Positional arguments: + file_name - the absolute path to a file + resources - the resources object that the file came from + export_path - the final destination of the file after export + """ + new_f = join(loc, relpath(file_name, resources.file_basepath[file_name])) + resources.file_basepath[join(export_path, new_f)] = export_path + return new_f -def get_program(n): - p = TEST_MAP[n].n - return p +def subtract_basepath(resources, export_path, loc=""): + """ Rewrite all of the basepaths with the export_path - -def get_test(p): - return Test(p) + Positional arguments: + resources - the resource object to rewrite the basepaths of + export_path - the final destination of the resources with respect to the + generated project files + """ + keys = ['s_sources', 'c_sources', 'cpp_sources', 'hex_files', + 'objects', 'libraries', 'inc_dirs', 'headers', 'linker_script', + 'lib_dirs'] + for key in keys: + vals = getattr(resources, key) + if isinstance(vals, set): + vals = list(vals) + if isinstance(vals, list): + new_vals = [] + for val in vals: + new_vals.append(rewrite_basepath(val, resources, export_path, + loc)) + if isinstance(getattr(resources, key), set): + setattr(resources, key, set(new_vals)) + else: + setattr(resources, key, new_vals) + elif vals: + setattr(resources, key, rewrite_basepath(vals, resources, + export_path, loc)) -def get_test_from_name(n): - if not n in TEST_MAP.keys(): - # Check if there is an alias for this in private_settings.py - if getattr(ps, "test_alias", None) is not None: - alias = ps.test_alias.get(n, "") - if not alias in TEST_MAP.keys(): - return None - else: - n = alias - else: - return None - return get_program(n) +def generate_project_files(resources, export_path, target, name, toolchain, ide, + macros=None): + """Generate the project files for a project + + Positional arguments: + resources - a Resources object containing all of the files needed to build + this project + export_path - location to place project files + name - name of the project + toolchain - a toolchain class that corresponds to the toolchain used by the + IDE or makefile + ide - IDE name to export to + + Optional arguments: + macros - additional macros that should be defined within the exported + project + """ + exporter_cls, _ = get_exporter_toolchain(ide) + exporter = exporter_cls(target, export_path, name, toolchain, + extra_symbols=macros, resources=resources) + exporter.generate() + files = exporter.generated_files + return files, exporter -def get_lib_symbols(macros, src, program): - # Some libraries have extra macros (called by exporter symbols) to we need to pass - # them to maintain compilation macros integrity between compiled library and - # header files we might use with it - lib_symbols = [] - if macros: - lib_symbols += macros - if src: - return lib_symbols - test = get_test(program) - for lib in LIBRARIES: - if lib['build_dir'] in test.dependencies: - lib_macros = lib.get('macros', None) - if lib_macros is not None: - lib_symbols.extend(lib_macros) +def zip_export(file_name, prefix, resources, project_files, inc_repos): + """Create a zip file from an exported project. + + Positional Parameters: + file_name - the file name of the resulting zip file + prefix - a directory name that will prefix the entire zip file's contents + resources - a resources object with files that must be included in the zip + project_files - a list of extra files to be added to the root of the prefix + directory + """ + with zipfile.ZipFile(file_name, "w") as zip_file: + for prj_file in project_files: + zip_file.write(prj_file, join(prefix, basename(prj_file))) + for loc, resource in resources.iteritems(): + for res in [resource] + resource.features.values(): + to_zip = ( + res.headers + res.s_sources + res.c_sources +\ + res.cpp_sources + res.libraries + res.hex_files + \ + [res.linker_script] + res.bin_files + res.objects + \ + res.json_files + res.lib_refs + res.lib_builds) + if inc_repos: + for directory in res.repo_dirs: + for root, _, files in walk(directory): + for repo_file in files: + source = join(root, repo_file) + to_zip.append(source) + res.file_basepath[source] = res.base_path + to_zip += res.repo_files + for source in to_zip: + if source: + zip_file.write( + source, + join(prefix, loc, + relpath(source, res.file_basepath[source]))) + for source in res.lib_builds: + target_dir, _ = splitext(source) + dest = join(prefix, loc, + relpath(target_dir, res.file_basepath[source]), + ".bld", "bldrc") + zip_file.write(source, dest) + -def setup_project(mcu, ide, program=None, source_dir=None, build=None): +def export_project(src_paths, export_path, target, ide, + libraries_paths=None, linker_script=None, clean=False, + notify=None, verbose=False, name=None, inc_dirs=None, + jobs=1, silent=False, extra_verbose=False, config=None, + macros=None, zip_proj=None, inc_repos=False, + build_profile=None): + """Generates a project file and creates a zip archive if specified + + Positional Arguments: + src_paths - a list of paths from which to find source files + export_path - a path specifying the location of generated project files + target - the mbed board/mcu for which to generate the executable + ide - the ide for which to generate the project fields - # Some libraries have extra macros (called by exporter symbols) to we need to pass - # them to maintain compilation macros integrity between compiled library and - # header files we might use with it - if source_dir: - # --source is used to generate IDE files to toolchain directly in the source tree and doesn't generate zip file - project_dir = source_dir - project_name = TESTS[program] if program else "Unnamed_Project" - project_temp = join(source_dir[0], 'projectfiles', '%s_%s' % (ide, mcu)) - mkdir(project_temp) + Keyword Arguments: + libraries_paths - paths to additional libraries + linker_script - path to the linker script for the specified target + clean - removes the export_path if it exists + notify - function is passed all events, and expected to handle notification + of the user, emit the events to a log, etc. + verbose - assigns the notify function to toolchains print_notify_verbose + name - project name + inc_dirs - additional include directories + jobs - number of threads + silent - silent build - no output + extra_verbose - assigns the notify function to toolchains + print_notify_verbose + config - toolchain's config object + macros - User-defined macros + zip_proj - string name of the zip archive you wish to creat (exclude arg + if you do not wish to create an archive + """ + + # Convert src_path to a list if needed + if isinstance(src_paths, dict): + paths = sum(src_paths.values(), []) + elif isinstance(src_paths, list): + paths = src_paths[:] else: - test = get_test(program) - if not build: - # Substitute the library builds with the sources - # TODO: Substitute also the other library build paths - if MBED_LIBRARIES in test.dependencies: - test.dependencies.remove(MBED_LIBRARIES) - test.dependencies.append(MBED_BASE) + paths = [src_paths] + + # Extend src_paths wit libraries_paths + if libraries_paths is not None: + paths.extend(libraries_paths) + + if not isinstance(src_paths, dict): + src_paths = {"": paths} + + # Export Directory + if exists(export_path) and clean: + rmtree(export_path) + if not exists(export_path): + makedirs(export_path) + + _, toolchain_name = get_exporter_toolchain(ide) + + # Pass all params to the unified prepare_resources() + toolchain = prepare_toolchain(paths, target, toolchain_name, + macros=macros, clean=clean, jobs=jobs, + notify=notify, silent=silent, verbose=verbose, + extra_verbose=extra_verbose, config=config, + build_profile=build_profile) + try: + # The first path will give the name to the library + if name is None: + name = basename(normpath(abspath(src_paths[0]))) - # Build the project with the same directory structure of the mbed online IDE - project_name = test.id - project_dir = [join(EXPORT_WORKSPACE, project_name)] - project_temp = EXPORT_TMP - setup_user_prj(project_dir[0], test.source_dir, test.dependencies) + # Call unified scan_resources + resource_dict = {loc: scan_resources(path, toolchain, inc_dirs=inc_dirs) + for loc, path in src_paths.iteritems()} + resources = Resources() + toolchain.build_dir = export_path + config_header = toolchain.get_config_header() + resources.headers.append(config_header) + resources.file_basepath[config_header] = dirname(config_header) - return project_dir, project_name, project_temp + if zip_proj: + subtract_basepath(resources, export_path) + for loc, res in resource_dict.iteritems(): + temp = copy.deepcopy(res) + subtract_basepath(temp, export_path, loc) + resources.add(temp) + else: + for _, res in resource_dict.iteritems(): + resources.add(res) + + # Change linker script if specified + if linker_script is not None: + resources.linker_script = linker_script + + files, exporter = generate_project_files(resources, export_path, + target, name, toolchain, ide, + macros=macros) + files.append(config_header) + if zip_proj: + if isinstance(zip_proj, basestring): + zip_export(join(export_path, zip_proj), name, resource_dict, files, inc_repos) + else: + zip_export(zip_proj, name, resource_dict, files, inc_repos) + + return exporter + except Exception as e: + toolchain.tool_error(str(e)) -def perform_export(dir, name, ide, mcu, temp, clean=False, zip=False, lib_symbols='', - sources_relative=False, progen_build=False): +def print_results(successes, failures, skips=None): + """ Print out the results of an export process - tmp_path, report = export(dir, name, ide, mcu, dir[0], temp, clean=clean, - make_zip=zip, extra_symbols=lib_symbols, sources_relative=sources_relative, - progen_build=progen_build) - return tmp_path, report + Positional arguments: + successes - The list of exports that succeeded + failures - The list of exports that failed - -def print_results(successes, failures, skips = []): + Keyword arguments: + skips - The list of exports that were skipped + """ print - if len(successes) > 0: + if successes: print "Successful: " for success in successes: print " * %s" % success - if len(failures) > 0: + if failures: print "Failed: " for failure in failures: print " * %s" % failure - if len(skips) > 0: + if skips: print "Skipped: " for skip in skips: print " * %s" % skip