the other jimmy / mbed-sdk-tools

Fork of mbed-sdk-tools by mbed official

Revision:
32:8ea194f6145b
Parent:
26:ed5e0d4e691e
--- 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