# Copyright (c) 2014-present PlatformIO # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # pylint: disable=no-member, no-self-use, unused-argument, too-many-lines # pylint: disable=too-many-instance-attributes, too-many-public-methods from __future__ import absolute_import import hashlib import os import re import sys from os.path import basename, commonprefix, isdir, isfile, join, realpath, sep import click import SCons.Scanner # pylint: disable=import-error from SCons.Script import ARGUMENTS # pylint: disable=import-error from SCons.Script import COMMAND_LINE_TARGETS # pylint: disable=import-error from SCons.Script import DefaultEnvironment # pylint: disable=import-error from platformio import exception, fs, util from platformio.builder.tools import platformio as piotool from platformio.compat import WINDOWS, hashlib_encode_data, string_types from platformio.managers.lib import LibraryManager from platformio.package.manifest.parser import ( ManifestParserError, ManifestParserFactory, ) from platformio.project.options import ProjectOptions class LibBuilderFactory(object): @staticmethod def new(env, path, verbose=int(ARGUMENTS.get("PIOVERBOSE", 0))): clsname = "UnknownLibBuilder" if isfile(join(path, "library.json")): clsname = "PlatformIOLibBuilder" else: used_frameworks = LibBuilderFactory.get_used_frameworks(env, path) common_frameworks = set(env.get("PIOFRAMEWORK", [])) & set(used_frameworks) if common_frameworks: clsname = "%sLibBuilder" % list(common_frameworks)[0].title() elif used_frameworks: clsname = "%sLibBuilder" % used_frameworks[0].title() obj = getattr(sys.modules[__name__], clsname)(env, path, verbose=verbose) assert isinstance(obj, LibBuilderBase) return obj @staticmethod def get_used_frameworks(env, path): if any( isfile(join(path, fname)) for fname in ("library.properties", "keywords.txt") ): return ["arduino"] if isfile(join(path, "module.json")): return ["mbed"] include_re = re.compile( r'^#include\s+(<|")(Arduino|mbed)\.h(<|")', flags=re.MULTILINE ) # check source files for root, _, files in os.walk(path, followlinks=True): if "mbed_lib.json" in files: return ["mbed"] for fname in files: if not fs.path_endswith_ext( fname, piotool.SRC_BUILD_EXT + piotool.SRC_HEADER_EXT ): continue content = fs.get_file_contents(join(root, fname)) if not content: continue if "Arduino.h" in content and include_re.search(content): return ["arduino"] if "mbed.h" in content and include_re.search(content): return ["mbed"] return [] class LibBuilderBase(object): CLASSIC_SCANNER = SCons.Scanner.C.CScanner() CCONDITIONAL_SCANNER = SCons.Scanner.C.CConditionalScanner() # Max depth of nested includes: # -1 = unlimited # 0 - disabled nesting # >0 - number of allowed nested includes CCONDITIONAL_SCANNER_DEPTH = 99 PARSE_SRC_BY_H_NAME = True _INCLUDE_DIRS_CACHE = None def __init__(self, env, path, manifest=None, verbose=False): self.env = env.Clone() self.envorigin = env.Clone() self.path = realpath(env.subst(path)) self.verbose = verbose try: self._manifest = manifest if manifest else self.load_manifest() except ManifestParserError: click.secho( "Warning! Ignoring broken library manifest in " + self.path, fg="yellow" ) self._manifest = {} self._is_dependent = False self._is_built = False self._depbuilders = list() self._circular_deps = list() self._processed_files = list() # reset source filter, could be overridden with extra script self.env["SRC_FILTER"] = "" # process extra options and append to build environment self.process_extra_options() def __repr__(self): return "%s(%r)" % (self.__class__, self.path) def __contains__(self, path): p1 = self.path p2 = path if WINDOWS: p1 = p1.lower() p2 = p2.lower() if p1 == p2: return True return commonprefix((p1 + sep, p2)) == p1 + sep @property def name(self): return self._manifest.get("name", basename(self.path)) @property def version(self): return self._manifest.get("version") @property def dependencies(self): return self._manifest.get("dependencies") @property def src_filter(self): return piotool.SRC_FILTER_DEFAULT + [ "-" % os.sep, "-" % os.sep, "-" % os.sep, "-" % os.sep, ] @property def include_dir(self): if not all(isdir(join(self.path, d)) for d in ("include", "src")): return None return join(self.path, "include") @property def src_dir(self): return join(self.path, "src") if isdir(join(self.path, "src")) else self.path def get_include_dirs(self): items = [] include_dir = self.include_dir if include_dir: items.append(include_dir) items.append(self.src_dir) return items @property def build_dir(self): lib_hash = hashlib.sha1(hashlib_encode_data(self.path)).hexdigest()[:3] return join("$BUILD_DIR", "lib%s" % lib_hash, basename(self.path)) @property def build_flags(self): return None @property def build_unflags(self): return None @property def extra_script(self): return None @property def depbuilders(self): return self._depbuilders @property def dependent(self): return self._is_dependent @property def is_built(self): return self._is_built @property def lib_archive(self): return self.env.GetProjectOption("lib_archive") @property def lib_ldf_mode(self): return self.env.GetProjectOption("lib_ldf_mode") @staticmethod def validate_ldf_mode(mode): ldf_modes = ProjectOptions["env.lib_ldf_mode"].type.choices if isinstance(mode, string_types): mode = mode.strip().lower() if mode in ldf_modes: return mode try: return ldf_modes[int(mode)] except (IndexError, ValueError): pass return ProjectOptions["env.lib_ldf_mode"].default @property def lib_compat_mode(self): return self.env.GetProjectOption("lib_compat_mode") @staticmethod def validate_compat_mode(mode): compat_modes = ProjectOptions["env.lib_compat_mode"].type.choices if isinstance(mode, string_types): mode = mode.strip().lower() if mode in compat_modes: return mode try: return compat_modes[int(mode)] except (IndexError, ValueError): pass return ProjectOptions["env.lib_compat_mode"].default def is_platforms_compatible(self, platforms): return True def is_frameworks_compatible(self, frameworks): return True def load_manifest(self): return {} def process_extra_options(self): with fs.cd(self.path): self.env.ProcessFlags(self.build_flags) if self.extra_script: self.env.SConscriptChdir(1) self.env.SConscript( realpath(self.extra_script), exports={"env": self.env, "pio_lib_builder": self}, ) self.env.ProcessUnFlags(self.build_unflags) def process_dependencies(self): if not self.dependencies: return for item in self.dependencies: found = False for lb in self.env.GetLibBuilders(): if item["name"] != lb.name: continue found = True if lb not in self.depbuilders: self.depend_recursive(lb) break if not found and self.verbose: sys.stderr.write( "Warning: Ignored `%s` dependency for `%s` " "library\n" % (item["name"], self.name) ) def get_search_files(self): items = [ join(self.src_dir, item) for item in self.env.MatchSourceFiles(self.src_dir, self.src_filter) ] include_dir = self.include_dir if include_dir: items.extend( [ join(include_dir, item) for item in self.env.MatchSourceFiles(include_dir) ] ) return items def _get_found_includes( # pylint: disable=too-many-branches self, search_files=None ): # all include directories if not LibBuilderBase._INCLUDE_DIRS_CACHE: LibBuilderBase._INCLUDE_DIRS_CACHE = [ self.env.Dir(d) for d in ProjectAsLibBuilder( self.envorigin, "$PROJECT_DIR" ).get_include_dirs() ] for lb in self.env.GetLibBuilders(): LibBuilderBase._INCLUDE_DIRS_CACHE.extend( [self.env.Dir(d) for d in lb.get_include_dirs()] ) # append self include directories include_dirs = [self.env.Dir(d) for d in self.get_include_dirs()] include_dirs.extend(LibBuilderBase._INCLUDE_DIRS_CACHE) result = [] for path in search_files or []: if path in self._processed_files: continue self._processed_files.append(path) try: assert "+" in self.lib_ldf_mode candidates = LibBuilderBase.CCONDITIONAL_SCANNER( self.env.File(path), self.env, tuple(include_dirs), depth=self.CCONDITIONAL_SCANNER_DEPTH, ) # mark candidates already processed via Conditional Scanner self._processed_files.extend( [ c.get_abspath() for c in candidates if c.get_abspath() not in self._processed_files ] ) except Exception as e: # pylint: disable=broad-except if self.verbose and "+" in self.lib_ldf_mode: sys.stderr.write( "Warning! Classic Pre Processor is used for `%s`, " "advanced has failed with `%s`\n" % (path, e) ) candidates = LibBuilderBase.CLASSIC_SCANNER( self.env.File(path), self.env, tuple(include_dirs) ) # print(path, [c.get_abspath() for c in candidates]) for item in candidates: if item not in result: result.append(item) if not self.PARSE_SRC_BY_H_NAME: continue _h_path = item.get_abspath() if not fs.path_endswith_ext(_h_path, piotool.SRC_HEADER_EXT): continue _f_part = _h_path[: _h_path.rindex(".")] for ext in piotool.SRC_C_EXT + piotool.SRC_CXX_EXT: if not isfile("%s.%s" % (_f_part, ext)): continue _c_path = self.env.File("%s.%s" % (_f_part, ext)) if _c_path not in result: result.append(_c_path) return result def depend_recursive(self, lb, search_files=None): def _already_depends(_lb): if self in _lb.depbuilders: return True for __lb in _lb.depbuilders: if _already_depends(__lb): return True return False # assert isinstance(lb, LibBuilderBase) if self != lb: if _already_depends(lb): if self.verbose: sys.stderr.write( "Warning! Circular dependencies detected " "between `%s` and `%s`\n" % (self.path, lb.path) ) self._circular_deps.append(lb) elif lb not in self._depbuilders: self._depbuilders.append(lb) LibBuilderBase._INCLUDE_DIRS_CACHE = None lb.search_deps_recursive(search_files) def search_deps_recursive(self, search_files=None): if not self._is_dependent: self._is_dependent = True self.process_dependencies() if self.lib_ldf_mode.startswith("deep"): search_files = self.get_search_files() # when LDF is disabled if self.lib_ldf_mode == "off": return lib_inc_map = {} for inc in self._get_found_includes(search_files): for lb in self.env.GetLibBuilders(): if inc.get_abspath() in lb: if lb not in lib_inc_map: lib_inc_map[lb] = [] lib_inc_map[lb].append(inc.get_abspath()) break for lb, lb_search_files in lib_inc_map.items(): self.depend_recursive(lb, lb_search_files) def build(self): libs = [] for lb in self._depbuilders: libs.extend(lb.build()) # copy shared information to self env for key in ("CPPPATH", "LIBPATH", "LIBS", "LINKFLAGS"): self.env.PrependUnique(**{key: lb.env.get(key)}) for lb in self._circular_deps: self.env.PrependUnique(CPPPATH=lb.get_include_dirs()) if self._is_built: return libs self._is_built = True self.env.PrependUnique(CPPPATH=self.get_include_dirs()) if self.lib_ldf_mode == "off": for lb in self.env.GetLibBuilders(): if self == lb or not lb.is_built: continue for key in ("CPPPATH", "LIBPATH", "LIBS", "LINKFLAGS"): self.env.PrependUnique(**{key: lb.env.get(key)}) if self.lib_archive: libs.append( self.env.BuildLibrary(self.build_dir, self.src_dir, self.src_filter) ) else: self.env.BuildSources(self.build_dir, self.src_dir, self.src_filter) return libs class UnknownLibBuilder(LibBuilderBase): pass class ArduinoLibBuilder(LibBuilderBase): def load_manifest(self): manifest_path = join(self.path, "library.properties") if not isfile(manifest_path): return {} return ManifestParserFactory.new_from_file(manifest_path).as_dict() def get_include_dirs(self): include_dirs = LibBuilderBase.get_include_dirs(self) if isdir(join(self.path, "src")): return include_dirs if isdir(join(self.path, "utility")): include_dirs.append(join(self.path, "utility")) return include_dirs @property def src_filter(self): src_dir = join(self.path, "src") if isdir(src_dir): src_filter = LibBuilderBase.src_filter.fget(self) for root, _, files in os.walk(src_dir, followlinks=True): found = False for fname in files: if fname.lower().endswith("asm"): found = True break if not found: continue rel_path = root.replace(src_dir, "") if rel_path.startswith(sep): rel_path = rel_path[1:] + sep src_filter.append("-<%s*.[aA][sS][mM]>" % rel_path) return src_filter src_filter = [] is_utility = isdir(join(self.path, "utility")) for ext in piotool.SRC_BUILD_EXT + piotool.SRC_HEADER_EXT: # arduino ide ignores files with .asm or .ASM extensions if ext.lower() == "asm": continue src_filter.append("+<*.%s>" % ext) if is_utility: src_filter.append("+" % (sep, ext)) return src_filter def is_frameworks_compatible(self, frameworks): return util.items_in_list(frameworks, ["arduino", "energia"]) def is_platforms_compatible(self, platforms): items = self._manifest.get("platforms", []) if not items: return LibBuilderBase.is_platforms_compatible(self, platforms) return util.items_in_list(platforms, items) class MbedLibBuilder(LibBuilderBase): def load_manifest(self): manifest_path = join(self.path, "module.json") if not isfile(manifest_path): return {} return ManifestParserFactory.new_from_file(manifest_path).as_dict() @property def include_dir(self): if isdir(join(self.path, "include")): return join(self.path, "include") return None @property def src_dir(self): if isdir(join(self.path, "source")): return join(self.path, "source") return LibBuilderBase.src_dir.fget(self) def get_include_dirs(self): include_dirs = LibBuilderBase.get_include_dirs(self) if self.path not in include_dirs: include_dirs.append(self.path) # library with module.json for p in self._manifest.get("extraIncludes", []): include_dirs.append(join(self.path, p)) # old mbed library without manifest, add to CPPPATH all folders if not self._manifest: for root, _, __ in os.walk(self.path): part = root.replace(self.path, "").lower() if any(s in part for s in ("%s." % sep, "test", "example")): continue if root not in include_dirs: include_dirs.append(root) return include_dirs def is_frameworks_compatible(self, frameworks): return util.items_in_list(frameworks, ["mbed"]) def process_extra_options(self): self._process_mbed_lib_confs() return super(MbedLibBuilder, self).process_extra_options() def _process_mbed_lib_confs(self): mbed_lib_paths = [ join(root, "mbed_lib.json") for root, _, files in os.walk(self.path) if "mbed_lib.json" in files ] if not mbed_lib_paths: return None mbed_config_path = None for p in self.env.get("CPPPATH"): mbed_config_path = join(self.env.subst(p), "mbed_config.h") if isfile(mbed_config_path): break mbed_config_path = None if not mbed_config_path: return None macros = {} for mbed_lib_path in mbed_lib_paths: macros.update(self._mbed_lib_conf_parse_macros(mbed_lib_path)) self._mbed_conf_append_macros(mbed_config_path, macros) return True @staticmethod def _mbed_normalize_macro(macro): name = macro value = None if "=" in macro: name, value = macro.split("=", 1) return dict(name=name, value=value) def _mbed_lib_conf_parse_macros(self, mbed_lib_path): macros = {} cppdefines = str(self.env.Flatten(self.env.subst("$CPPDEFINES"))) manifest = fs.load_json(mbed_lib_path) # default macros for macro in manifest.get("macros", []): macro = self._mbed_normalize_macro(macro) macros[macro["name"]] = macro # configuration items for key, options in manifest.get("config", {}).items(): if "value" not in options: continue macros[key] = dict( name=options.get("macro_name"), value=options.get("value") ) # overrode items per target for target, options in manifest.get("target_overrides", {}).items(): if target != "*" and "TARGET_" + target not in cppdefines: continue for macro in options.get("target.macros_add", []): macro = self._mbed_normalize_macro(macro) macros[macro["name"]] = macro for key, value in options.items(): if not key.startswith("target.") and key in macros: macros[key]["value"] = value # normalize macro names for key, macro in macros.items(): if not macro["name"]: macro["name"] = key if "." not in macro["name"]: macro["name"] = "%s.%s" % (manifest.get("name"), macro["name"]) macro["name"] = re.sub( r"[^a-z\d]+", "_", macro["name"], flags=re.I ).upper() macro["name"] = "MBED_CONF_" + macro["name"] if isinstance(macro["value"], bool): macro["value"] = 1 if macro["value"] else 0 return {macro["name"]: macro["value"] for macro in macros.values()} def _mbed_conf_append_macros(self, mbed_config_path, macros): lines = [] with open(mbed_config_path) as fp: for line in fp.readlines(): line = line.strip() if line == "#endif": lines.append("// PlatformIO Library Dependency Finder (LDF)") lines.extend( [ "#define %s %s" % (name, value if value is not None else "") for name, value in macros.items() ] ) lines.append("") if not line.startswith("#define"): lines.append(line) continue tokens = line.split() if len(tokens) < 2 or tokens[1] not in macros: lines.append(line) lines.append("") with open(mbed_config_path, "w") as fp: fp.write("\n".join(lines)) class PlatformIOLibBuilder(LibBuilderBase): def load_manifest(self): manifest_path = join(self.path, "library.json") if not isfile(manifest_path): return {} return ManifestParserFactory.new_from_file(manifest_path).as_dict() def _has_arduino_manifest(self): return isfile(join(self.path, "library.properties")) @property def include_dir(self): if "includeDir" in self._manifest.get("build", {}): with fs.cd(self.path): return realpath(self._manifest.get("build").get("includeDir")) return LibBuilderBase.include_dir.fget(self) @property def src_dir(self): if "srcDir" in self._manifest.get("build", {}): with fs.cd(self.path): return realpath(self._manifest.get("build").get("srcDir")) return LibBuilderBase.src_dir.fget(self) @property def src_filter(self): if "srcFilter" in self._manifest.get("build", {}): return self._manifest.get("build").get("srcFilter") if self.env["SRC_FILTER"]: return self.env["SRC_FILTER"] if self._has_arduino_manifest(): return ArduinoLibBuilder.src_filter.fget(self) return LibBuilderBase.src_filter.fget(self) @property def build_flags(self): if "flags" in self._manifest.get("build", {}): return self._manifest.get("build").get("flags") return LibBuilderBase.build_flags.fget(self) @property def build_unflags(self): if "unflags" in self._manifest.get("build", {}): return self._manifest.get("build").get("unflags") return LibBuilderBase.build_unflags.fget(self) @property def extra_script(self): if "extraScript" in self._manifest.get("build", {}): return self._manifest.get("build").get("extraScript") return LibBuilderBase.extra_script.fget(self) @property def lib_archive(self): unique_value = "_not_declared_%s" % id(self) global_value = self.env.GetProjectOption("lib_archive", unique_value) if global_value != unique_value: return global_value return self._manifest.get("build", {}).get( "libArchive", LibBuilderBase.lib_archive.fget(self) ) @property def lib_ldf_mode(self): return self.validate_ldf_mode( self._manifest.get("build", {}).get( "libLDFMode", LibBuilderBase.lib_ldf_mode.fget(self) ) ) @property def lib_compat_mode(self): return self.validate_compat_mode( self._manifest.get("build", {}).get( "libCompatMode", LibBuilderBase.lib_compat_mode.fget(self) ) ) def is_platforms_compatible(self, platforms): items = self._manifest.get("platforms") if not items: return LibBuilderBase.is_platforms_compatible(self, platforms) return util.items_in_list(platforms, items) def is_frameworks_compatible(self, frameworks): items = self._manifest.get("frameworks") if not items: return LibBuilderBase.is_frameworks_compatible(self, frameworks) return util.items_in_list(frameworks, items) def get_include_dirs(self): include_dirs = LibBuilderBase.get_include_dirs(self) # backwards compatibility with PlatformIO 2.0 if ( "build" not in self._manifest and self._has_arduino_manifest() and not isdir(join(self.path, "src")) and isdir(join(self.path, "utility")) ): include_dirs.append(join(self.path, "utility")) for path in self.env.get("CPPPATH", []): if path not in self.envorigin.get("CPPPATH", []): include_dirs.append(self.env.subst(path)) return include_dirs class ProjectAsLibBuilder(LibBuilderBase): def __init__(self, env, *args, **kwargs): # backup original value, will be reset in base.__init__ project_src_filter = env.get("SRC_FILTER") super(ProjectAsLibBuilder, self).__init__(env, *args, **kwargs) self.env["SRC_FILTER"] = project_src_filter @property def include_dir(self): include_dir = self.env.subst("$PROJECT_INCLUDE_DIR") return include_dir if isdir(include_dir) else None @property def src_dir(self): return self.env.subst("$PROJECT_SRC_DIR") def get_include_dirs(self): include_dirs = [] project_include_dir = self.env.subst("$PROJECT_INCLUDE_DIR") if isdir(project_include_dir): include_dirs.append(project_include_dir) for include_dir in LibBuilderBase.get_include_dirs(self): if include_dir not in include_dirs: include_dirs.append(include_dir) return include_dirs def get_search_files(self): # project files items = LibBuilderBase.get_search_files(self) # test files if "__test" in COMMAND_LINE_TARGETS: items.extend( [ join("$PROJECT_TEST_DIR", item) for item in self.env.MatchSourceFiles( "$PROJECT_TEST_DIR", "$PIOTEST_SRC_FILTER" ) ] ) return items @property def lib_ldf_mode(self): mode = LibBuilderBase.lib_ldf_mode.fget(self) if not mode.startswith("chain"): return mode # parse all project files return "deep+" if "+" in mode else "deep" @property def src_filter(self): return self.env.get("SRC_FILTER") or LibBuilderBase.src_filter.fget(self) @property def dependencies(self): return self.env.GetProjectOption("lib_deps", []) def process_extra_options(self): # skip for project, options are already processed pass def install_dependencies(self): def _is_builtin(uri): for lb in self.env.GetLibBuilders(): if lb.name == uri: return True return False not_found_uri = [] for uri in self.dependencies: # check if built-in library if _is_builtin(uri): continue found = False for storage_dir in self.env.GetLibSourceDirs(): lm = LibraryManager(storage_dir) if lm.get_package_dir(*lm.parse_pkg_uri(uri)): found = True break if not found: not_found_uri.append(uri) did_install = False lm = LibraryManager(self.env.subst(join("$PROJECT_LIBDEPS_DIR", "$PIOENV"))) for uri in not_found_uri: try: lm.install(uri) did_install = True except (exception.LibNotFound, exception.InternetIsOffline) as e: click.secho("Warning! %s" % e, fg="yellow") # reset cache if did_install: DefaultEnvironment().Replace(__PIO_LIB_BUILDERS=None) def process_dependencies(self): # pylint: disable=too-many-branches for uri in self.dependencies: found = False for storage_dir in self.env.GetLibSourceDirs(): if found: break lm = LibraryManager(storage_dir) lib_dir = lm.get_package_dir(*lm.parse_pkg_uri(uri)) if not lib_dir: continue for lb in self.env.GetLibBuilders(): if lib_dir != lb.path: continue if lb not in self.depbuilders: self.depend_recursive(lb) found = True break if found: continue # look for built-in libraries by a name # which don't have package manifest for lb in self.env.GetLibBuilders(): if lb.name != uri: continue if lb not in self.depbuilders: self.depend_recursive(lb) found = True break def build(self): self._is_built = True # do not build Project now result = LibBuilderBase.build(self) self.env.PrependUnique(CPPPATH=self.get_include_dirs()) return result def GetLibSourceDirs(env): items = env.GetProjectOption("lib_extra_dirs", []) items.extend(env["LIBSOURCE_DIRS"]) return [ env.subst(fs.expanduser(item) if item.startswith("~") else item) for item in items ] def IsCompatibleLibBuilder(env, lb, verbose=int(ARGUMENTS.get("PIOVERBOSE", 0))): compat_mode = lb.lib_compat_mode if lb.name in env.GetProjectOption("lib_ignore", []): if verbose: sys.stderr.write("Ignored library %s\n" % lb.path) return None if compat_mode == "strict" and not lb.is_platforms_compatible(env["PIOPLATFORM"]): if verbose: sys.stderr.write("Platform incompatible library %s\n" % lb.path) return False if compat_mode in ("soft", "strict") and not lb.is_frameworks_compatible( env.get("PIOFRAMEWORK", []) ): if verbose: sys.stderr.write("Framework incompatible library %s\n" % lb.path) return False return True def GetLibBuilders(env): # pylint: disable=too-many-branches if DefaultEnvironment().get("__PIO_LIB_BUILDERS", None) is not None: return sorted( DefaultEnvironment()["__PIO_LIB_BUILDERS"], key=lambda lb: 0 if lb.dependent else 1, ) DefaultEnvironment().Replace(__PIO_LIB_BUILDERS=[]) verbose = int(ARGUMENTS.get("PIOVERBOSE", 0)) found_incompat = False for storage_dir in env.GetLibSourceDirs(): storage_dir = realpath(storage_dir) if not isdir(storage_dir): continue for item in sorted(os.listdir(storage_dir)): lib_dir = join(storage_dir, item) if item == "__cores__" or not isdir(lib_dir): continue try: lb = LibBuilderFactory.new(env, lib_dir) except exception.InvalidJSONFile: if verbose: sys.stderr.write( "Skip library with broken manifest: %s\n" % lib_dir ) continue if env.IsCompatibleLibBuilder(lb): DefaultEnvironment().Append(__PIO_LIB_BUILDERS=[lb]) else: found_incompat = True for lb in env.get("EXTRA_LIB_BUILDERS", []): if env.IsCompatibleLibBuilder(lb): DefaultEnvironment().Append(__PIO_LIB_BUILDERS=[lb]) else: found_incompat = True if verbose and found_incompat: sys.stderr.write( 'More details about "Library Compatibility Mode": ' "https://docs.platformio.org/page/librarymanager/ldf.html#" "ldf-compat-mode\n" ) return DefaultEnvironment()["__PIO_LIB_BUILDERS"] def ConfigureProjectLibBuilder(env): def _get_vcs_info(lb): path = LibraryManager.get_src_manifest_path(lb.path) return fs.load_json(path) if path else None def _correct_found_libs(lib_builders): # build full dependency graph found_lbs = [lb for lb in lib_builders if lb.dependent] for lb in lib_builders: if lb in found_lbs: lb.search_deps_recursive(lb.get_search_files()) for lb in lib_builders: for deplb in lb.depbuilders[:]: if deplb not in found_lbs: lb.depbuilders.remove(deplb) def _print_deps_tree(root, level=0): margin = "| " * (level) for lb in root.depbuilders: title = "<%s>" % lb.name vcs_info = _get_vcs_info(lb) if lb.version: title += " %s" % lb.version if vcs_info and vcs_info.get("version"): title += " #%s" % vcs_info.get("version") click.echo("%s|-- %s" % (margin, title), nl=False) if int(ARGUMENTS.get("PIOVERBOSE", 0)): if vcs_info: click.echo(" [%s]" % vcs_info.get("url"), nl=False) click.echo(" (", nl=False) click.echo(lb.path, nl=False) click.echo(")", nl=False) click.echo("") if lb.depbuilders: _print_deps_tree(lb, level + 1) project = ProjectAsLibBuilder(env, "$PROJECT_DIR") ldf_mode = LibBuilderBase.lib_ldf_mode.fget(project) click.echo("LDF: Library Dependency Finder -> http://bit.ly/configure-pio-ldf") click.echo( "LDF Modes: Finder ~ %s, Compatibility ~ %s" % (ldf_mode, project.lib_compat_mode) ) project.install_dependencies() lib_builders = env.GetLibBuilders() click.echo("Found %d compatible libraries" % len(lib_builders)) click.echo("Scanning dependencies...") project.search_deps_recursive() if ldf_mode.startswith("chain") and project.depbuilders: _correct_found_libs(lib_builders) if project.depbuilders: click.echo("Dependency Graph") _print_deps_tree(project) else: click.echo("No dependencies") return project def exists(_): return True def generate(env): env.AddMethod(GetLibSourceDirs) env.AddMethod(IsCompatibleLibBuilder) env.AddMethod(GetLibBuilders) env.AddMethod(ConfigureProjectLibBuilder) return env