Handle "dependencies" from library and project when build libraries // Issue #709

This commit is contained in:
Ivan Kravets
2016-07-31 00:00:58 +03:00
parent b364389541
commit 74af8a5c39
4 changed files with 108 additions and 36 deletions

View File

@ -39,10 +39,15 @@ How works Library Dependency Finder (LDF)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Library Dependency Finder is a part of PlatformIO Library Build System. It Library Dependency Finder is a part of PlatformIO Library Build System. It
operates with the header files (``*.h, *.hpp``) and looks for operates with the C/C++ source files and looks for ``#include <...>``
``#include <...>`` directives. What is more, LDF interprets C Preprocessor directives. Also, LDF interprets C Preprocessor conditional macros
conditional macros (``#ifdef ...``, etc.). Library Dependency Finder starts (``#if``, ``ifdef``, etc.). Library Dependency Finder starts
work from analyzing source files from :ref:`projectconf_pio_src_dir`. 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 There are different library storages where Library Dependency Finder looks for
dependencies. These storages/folders have priority. LDF operates in the next dependencies. These storages/folders have priority. LDF operates in the next

View File

@ -381,6 +381,7 @@ A list of dependent libraries. They will be installed automatically with
Allowed requirements for dependent library: Allowed requirements for dependent library:
* ``name`` | Type: ``String`` * ``name`` | Type: ``String``
* ``version`` | Type: ``String``
* ``authors`` | Type: ``String`` or ``Array`` * ``authors`` | Type: ``String`` or ``Array``
* ``frameworks`` | Type: ``String`` or ``Array`` * ``frameworks`` | Type: ``String`` or ``Array``
* ``platforms`` | Type: ``String`` or ``Array`` * ``platforms`` | Type: ``String`` or ``Array``
@ -401,10 +402,23 @@ Example:
}, },
{ {
"name": "Library-Bar", "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`. See more ``library.json`` :ref:`library_creating_examples`.

View File

@ -14,7 +14,7 @@
import sys import sys
VERSION = (3, 0, "0.dev13") VERSION = (3, 0, "0.dev14")
__version__ = ".".join([str(s) for s in VERSION]) __version__ = ".".join([str(s) for s in VERSION])
__title__ = "platformio" __title__ = "platformio"

View File

@ -82,8 +82,8 @@ class LibBuilderBase(object): # pylint: disable=too-many-instance-attributes
self.path = env.subst(path) self.path = env.subst(path)
self._manifest = self.load_manifest() self._manifest = self.load_manifest()
self._is_dependent = False self._is_dependent = False
self._deps = tuple() self._depbuilders = tuple()
self._scanner_visited = tuple() self._scanned_paths = tuple()
self._built_node = None self._built_node = None
# process extra options and append to build environment # process extra options and append to build environment
@ -103,6 +103,31 @@ class LibBuilderBase(object): # pylint: disable=too-many-instance-attributes
def version(self): def version(self):
return self._manifest.get("version") 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 @property
def src_filter(self): def src_filter(self):
return piotool.SRC_FILTER_DEFAULT + [ return piotool.SRC_FILTER_DEFAULT + [
@ -136,8 +161,8 @@ class LibBuilderBase(object): # pylint: disable=too-many-instance-attributes
return True return True
@property @property
def dependencies(self): def depbuilders(self):
return self._deps return self._depbuilders
@property @property
def dependent(self): def dependent(self):
@ -171,20 +196,20 @@ class LibBuilderBase(object): # pylint: disable=too-many-instance-attributes
assert isinstance(search_paths, tuple) assert isinstance(search_paths, tuple)
deep_search = self.env.get("LIB_DEEP_SEARCH", "true").lower() == "true" 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): isinstance(self, ProjectAsLibBuilder) or deep_search):
for item in self.env.MatchSourceFiles(self.src_dir, for item in self.env.MatchSourceFiles(self.src_dir,
self.src_filter): self.src_filter):
path = join(self.src_dir, item) 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): path not in search_paths):
search_paths += (path, ) search_paths += (path, )
_search_paths = tuple() _search_paths = tuple()
for path in search_paths: for path in search_paths:
if path not in self._scanner_visited: if path not in self._scanned_paths:
_search_paths += (path, ) _search_paths += (path, )
self._scanner_visited += (path, ) self._scanned_paths += (path, )
return _search_paths return _search_paths
@ -207,16 +232,46 @@ class LibBuilderBase(object): # pylint: disable=too-many-instance-attributes
result += (inc, ) result += (inc, )
return result return result
def depends_on(self, lb): def depend_recursive(self, lb, lib_builders, search_paths=None):
assert isinstance(lb, LibBuilderBase) assert isinstance(lb, LibBuilderBase)
if self in lb.dependencies: if self != lb:
sys.stderr.write("Warning! Circular dependencies detected " if self in lb.depbuilders:
"between `%s` and `%s`\n" % (self.path, lb.path)) sys.stderr.write("Warning! Circular dependencies detected "
elif lb not in self._deps: "between `%s` and `%s`\n" %
self._deps += (lb, ) (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 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 = {} lib_inc_map = {}
for inc in self._get_found_includes(lib_builders, search_paths): for inc in self._get_found_includes(lib_builders, search_paths):
for lb in lib_builders: for lb in lib_builders:
@ -227,13 +282,11 @@ class LibBuilderBase(object): # pylint: disable=too-many-instance-attributes
break break
for lb, lb_src_files in lib_inc_map.items(): for lb, lb_src_files in lib_inc_map.items():
if lb != self and lb not in self.dependencies: self.depend_recursive(lb, lib_builders, lb_src_files)
self.depends_on(lb)
lb.search_dependencies(lib_builders, lb_src_files)
def build(self): def build(self):
libs = [] libs = []
for lb in self.dependencies: for lb in self.depbuilders:
libs.extend(lb.build()) libs.extend(lb.build())
# copy shared information to self env # copy shared information to self env
for key in ("CPPPATH", "LIBPATH", "LIBS", "LINKFLAGS"): for key in ("CPPPATH", "LIBPATH", "LIBS", "LINKFLAGS"):
@ -265,15 +318,15 @@ class ProjectAsLibBuilder(LibBuilderBase):
# skip for project, options are already processed # skip for project, options are already processed
pass 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 lib_name in self.env.get("LIB_FORCE", []):
for lb in lib_builders: for lb in lib_builders:
if lb.name == lib_name and lb not in self.dependencies: if lb.name == lib_name:
self.depends_on(lb) if lb not in self.depbuilders:
lb.search_dependencies(lib_builders) self.depend_recursive(lb, lib_builders)
break break
return LibBuilderBase.search_dependencies(self, lib_builders, return LibBuilderBase.search_deps_recursive(self, lib_builders,
search_paths) search_paths)
def build(self): def build(self):
# dummy mark that project is built # dummy mark that project is built
@ -422,7 +475,7 @@ def GetLibBuilders(env):
lb = LibBuilderFactory.new(env, join(libs_dir, item)) lb = LibBuilderFactory.new(env, join(libs_dir, item))
if lb.name in env.get("LIB_IGNORE", []): if lb.name in env.get("LIB_IGNORE", []):
if not env.GetOption("silent"): if not env.GetOption("silent"):
print "Ignored library " + lb.path sys.stderr.write("Ignored library %s\n" % lb.path)
continue continue
if compat_level > 1 and not lb.is_platform_compatible(env[ if compat_level > 1 and not lb.is_platform_compatible(env[
'PIOPLATFORM']): 'PIOPLATFORM']):
@ -444,14 +497,14 @@ def BuildDependentLibraries(env, src_dir):
def print_deps_tree(root, level=0): def print_deps_tree(root, level=0):
margin = "| " * (level) margin = "| " * (level)
for lb in root.dependencies: for lb in root.depbuilders:
title = "<%s>" % lb.name title = "<%s>" % lb.name
if lb.version: if lb.version:
title += " v%s" % lb.version title += " v%s" % lb.version
if not env.GetOption("silent"): if not env.GetOption("silent"):
title += " (%s)" % lb.path title += " (%s)" % lb.path
print "%s|-- %s" % (margin, title) print "%s|-- %s" % (margin, title)
if lb.dependencies: if lb.depbuilders:
print_deps_tree(lb, level + 1) print_deps_tree(lb, level + 1)
lib_builders = env.GetLibBuilders() lib_builders = env.GetLibBuilders()
@ -461,9 +514,9 @@ def BuildDependentLibraries(env, src_dir):
project = ProjectAsLibBuilder(env, src_dir) project = ProjectAsLibBuilder(env, src_dir)
project.env = env 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 "Library Dependency Map"
print_deps_tree(project) print_deps_tree(project)
else: else: