From 74af8a5c397f728b8d4337084affe73f4b950a8c Mon Sep 17 00:00:00 2001 From: Ivan Kravets Date: Sun, 31 Jul 2016 00:00:58 +0300 Subject: [PATCH] Handle "dependencies" from library and project when build libraries // Issue #709 --- docs/faq.rst | 13 +++- docs/librarymanager/config.rst | 16 +++- platformio/__init__.py | 2 +- platformio/builder/tools/piolib.py | 113 +++++++++++++++++++++-------- 4 files changed, 108 insertions(+), 36 deletions(-) diff --git a/docs/faq.rst b/docs/faq.rst index bd55be30..72b13163 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -39,10 +39,15 @@ How works Library Dependency Finder (LDF) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Library Dependency Finder is a part of PlatformIO Library Build System. It -operates with the header files (``*.h, *.hpp``) and looks for -``#include <...>`` directives. What is more, LDF interprets C Preprocessor -conditional macros (``#ifdef ...``, etc.). Library Dependency Finder starts -work from analyzing source files from :ref:`projectconf_pio_src_dir`. +operates with the C/C++ source files and looks for ``#include <...>`` +directives. Also, LDF interprets C Preprocessor conditional macros +(``#if``, ``ifdef``, etc.). Library Dependency Finder starts +work from analyzing source files from :ref:`projectconf_pio_src_dir` by default. + +If project or library contains own ``dependencies`` list (see +:ref:`libjson_dependencies`), the LDF will not looking for dependencies in +the source code. The specified libraries will be built automatically without +check. There are different library storages where Library Dependency Finder looks for dependencies. These storages/folders have priority. LDF operates in the next diff --git a/docs/librarymanager/config.rst b/docs/librarymanager/config.rst index 13c0745f..1c0e5354 100644 --- a/docs/librarymanager/config.rst +++ b/docs/librarymanager/config.rst @@ -381,6 +381,7 @@ A list of dependent libraries. They will be installed automatically with Allowed requirements for dependent library: * ``name`` | Type: ``String`` +* ``version`` | Type: ``String`` * ``authors`` | Type: ``String`` or ``Array`` * ``frameworks`` | Type: ``String`` or ``Array`` * ``platforms`` | Type: ``String`` or ``Array`` @@ -401,10 +402,23 @@ Example: }, { "name": "Library-Bar", - "frameworks": "FrameworkFoo, FrameworkBar" + "version": "~1.2.3" + }, + { + "name": "lib-from-repo", + "version": "https://github.com/user/package.git#1.2.3" } ] +A short definition of dependencies is allowed: + +.. code-block:: javascript + + "dependencies": { + "mylib": "1.2.3", + "lib-from-repo": "githubuser/package" + } + See more ``library.json`` :ref:`library_creating_examples`. diff --git a/platformio/__init__.py b/platformio/__init__.py index 735c7548..5f0228e9 100644 --- a/platformio/__init__.py +++ b/platformio/__init__.py @@ -14,7 +14,7 @@ import sys -VERSION = (3, 0, "0.dev13") +VERSION = (3, 0, "0.dev14") __version__ = ".".join([str(s) for s in VERSION]) __title__ = "platformio" diff --git a/platformio/builder/tools/piolib.py b/platformio/builder/tools/piolib.py index ebddb279..ce1a110a 100644 --- a/platformio/builder/tools/piolib.py +++ b/platformio/builder/tools/piolib.py @@ -82,8 +82,8 @@ class LibBuilderBase(object): # pylint: disable=too-many-instance-attributes self.path = env.subst(path) self._manifest = self.load_manifest() self._is_dependent = False - self._deps = tuple() - self._scanner_visited = tuple() + self._depbuilders = tuple() + self._scanned_paths = tuple() self._built_node = None # process extra options and append to build environment @@ -103,6 +103,31 @@ class LibBuilderBase(object): # pylint: disable=too-many-instance-attributes def version(self): return self._manifest.get("version") + @property + def dependencies(self): + deps = self._manifest.get("dependencies") + if not deps: + return deps + items = [] + if isinstance(deps, dict): + if "name" in deps: + items.append(deps) + else: + for name, version in deps.items(): + items.append({"name": name, "version": version}) + elif isinstance(deps, list): + items = [d for d in deps if "name" in d] + for item in items: + for k in ("frameworks", "platforms"): + if k not in item or isinstance(k, list): + continue + if item[k] == "*": + del item[k] + elif isinstance(item[k], basestring): + item[k] = [i.strip() for i in item[k].split(",") + if i.strip()] + return items + @property def src_filter(self): return piotool.SRC_FILTER_DEFAULT + [ @@ -136,8 +161,8 @@ class LibBuilderBase(object): # pylint: disable=too-many-instance-attributes return True @property - def dependencies(self): - return self._deps + def depbuilders(self): + return self._depbuilders @property def dependent(self): @@ -171,20 +196,20 @@ class LibBuilderBase(object): # pylint: disable=too-many-instance-attributes assert isinstance(search_paths, tuple) deep_search = self.env.get("LIB_DEEP_SEARCH", "true").lower() == "true" - if not self._scanner_visited and ( + if not self._scanned_paths and ( isinstance(self, ProjectAsLibBuilder) or deep_search): for item in self.env.MatchSourceFiles(self.src_dir, self.src_filter): path = join(self.src_dir, item) - if (path not in self._scanner_visited and + if (path not in self._scanned_paths and path not in search_paths): search_paths += (path, ) _search_paths = tuple() for path in search_paths: - if path not in self._scanner_visited: + if path not in self._scanned_paths: _search_paths += (path, ) - self._scanner_visited += (path, ) + self._scanned_paths += (path, ) return _search_paths @@ -207,16 +232,46 @@ class LibBuilderBase(object): # pylint: disable=too-many-instance-attributes result += (inc, ) return result - def depends_on(self, lb): + def depend_recursive(self, lb, lib_builders, search_paths=None): assert isinstance(lb, LibBuilderBase) - if self in lb.dependencies: - sys.stderr.write("Warning! Circular dependencies detected " - "between `%s` and `%s`\n" % (self.path, lb.path)) - elif lb not in self._deps: - self._deps += (lb, ) + if self != lb: + if self in lb.depbuilders: + sys.stderr.write("Warning! Circular dependencies detected " + "between `%s` and `%s`\n" % + (self.path, lb.path)) + elif lb not in self._depbuilders: + self._depbuilders += (lb, ) + lb.search_deps_recursive(lib_builders, search_paths) - def search_dependencies(self, lib_builders, search_paths=None): + def search_deps_recursive(self, lib_builders, search_paths=None): self._is_dependent = True + + # if dependencies are specified, don't use automatic finder + if self.dependencies: + for item in self.dependencies: + found = False + for lb in lib_builders: + if item['name'] != lb.name: + continue + elif "frameworks" in item and \ + not any([lb.is_framework_compatible(f) + for f in item["frameworks"]]): + continue + elif "platforms" in item and \ + not any([lb.is_platform_compatible(p) + for p in item["platforms"]]): + continue + found = True + self.depend_recursive(lb, lib_builders) + break + + if not found: + sys.stderr.write( + "Error: Could not find `%s` dependency for `%s` " + "library\n" % (item['name'], self.name)) + self.env.Exit(2) + return + lib_inc_map = {} for inc in self._get_found_includes(lib_builders, search_paths): for lb in lib_builders: @@ -227,13 +282,11 @@ class LibBuilderBase(object): # pylint: disable=too-many-instance-attributes break for lb, lb_src_files in lib_inc_map.items(): - if lb != self and lb not in self.dependencies: - self.depends_on(lb) - lb.search_dependencies(lib_builders, lb_src_files) + self.depend_recursive(lb, lib_builders, lb_src_files) def build(self): libs = [] - for lb in self.dependencies: + for lb in self.depbuilders: libs.extend(lb.build()) # copy shared information to self env for key in ("CPPPATH", "LIBPATH", "LIBS", "LINKFLAGS"): @@ -265,15 +318,15 @@ class ProjectAsLibBuilder(LibBuilderBase): # skip for project, options are already processed pass - def search_dependencies(self, lib_builders, search_paths=None): + def search_deps_recursive(self, lib_builders, search_paths=None): for lib_name in self.env.get("LIB_FORCE", []): for lb in lib_builders: - if lb.name == lib_name and lb not in self.dependencies: - self.depends_on(lb) - lb.search_dependencies(lib_builders) + if lb.name == lib_name: + if lb not in self.depbuilders: + self.depend_recursive(lb, lib_builders) break - return LibBuilderBase.search_dependencies(self, lib_builders, - search_paths) + return LibBuilderBase.search_deps_recursive(self, lib_builders, + search_paths) def build(self): # dummy mark that project is built @@ -422,7 +475,7 @@ def GetLibBuilders(env): lb = LibBuilderFactory.new(env, join(libs_dir, item)) if lb.name in env.get("LIB_IGNORE", []): if not env.GetOption("silent"): - print "Ignored library " + lb.path + sys.stderr.write("Ignored library %s\n" % lb.path) continue if compat_level > 1 and not lb.is_platform_compatible(env[ 'PIOPLATFORM']): @@ -444,14 +497,14 @@ def BuildDependentLibraries(env, src_dir): def print_deps_tree(root, level=0): margin = "| " * (level) - for lb in root.dependencies: + for lb in root.depbuilders: title = "<%s>" % lb.name if lb.version: title += " v%s" % lb.version if not env.GetOption("silent"): title += " (%s)" % lb.path print "%s|-- %s" % (margin, title) - if lb.dependencies: + if lb.depbuilders: print_deps_tree(lb, level + 1) lib_builders = env.GetLibBuilders() @@ -461,9 +514,9 @@ def BuildDependentLibraries(env, src_dir): project = ProjectAsLibBuilder(env, src_dir) project.env = env - project.search_dependencies(lib_builders) + project.search_deps_recursive(lib_builders) - if project.dependencies: + if project.depbuilders: print "Library Dependency Map" print_deps_tree(project) else: