forked from platformio/platformio-core
Handle "dependencies" from library and project when build libraries // Issue #709
This commit is contained in:
13
docs/faq.rst
13
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
|
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
|
||||||
|
@ -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`.
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
@ -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:
|
||||||
|
Reference in New Issue
Block a user