Switch library manager to the new package manager

This commit is contained in:
Ivan Kravets
2020-08-12 13:27:05 +03:00
parent 2dd69e21c0
commit 893ca1b328
30 changed files with 1318 additions and 915 deletions

View File

@ -34,11 +34,13 @@ 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.exception import UnknownPackageError
from platformio.package.manager.library import LibraryPackageManager
from platformio.package.manifest.parser import (
ManifestParserError,
ManifestParserFactory,
)
from platformio.package.meta import PackageSourceItem
from platformio.project.options import ProjectOptions
@ -851,34 +853,36 @@ class ProjectAsLibBuilder(LibBuilderBase):
pass
def install_dependencies(self):
def _is_builtin(uri):
def _is_builtin(spec):
for lb in self.env.GetLibBuilders():
if lb.name == uri:
if lb.name == spec:
return True
return False
not_found_uri = []
for uri in self.dependencies:
not_found_specs = []
for spec in self.dependencies:
# check if built-in library
if _is_builtin(uri):
if _is_builtin(spec):
continue
found = False
for storage_dir in self.env.GetLibSourceDirs():
lm = LibraryManager(storage_dir)
if lm.get_package_dir(*lm.parse_pkg_uri(uri)):
lm = LibraryPackageManager(storage_dir)
if lm.get_package(spec):
found = True
break
if not found:
not_found_uri.append(uri)
not_found_specs.append(spec)
did_install = False
lm = LibraryManager(self.env.subst(join("$PROJECT_LIBDEPS_DIR", "$PIOENV")))
for uri in not_found_uri:
lm = LibraryPackageManager(
self.env.subst(join("$PROJECT_LIBDEPS_DIR", "$PIOENV"))
)
for spec in not_found_specs:
try:
lm.install(uri)
lm.install(spec)
did_install = True
except (exception.LibNotFound, exception.InternetIsOffline) as e:
except (UnknownPackageError, exception.InternetIsOffline) as e:
click.secho("Warning! %s" % e, fg="yellow")
# reset cache
@ -886,17 +890,17 @@ class ProjectAsLibBuilder(LibBuilderBase):
DefaultEnvironment().Replace(__PIO_LIB_BUILDERS=None)
def process_dependencies(self): # pylint: disable=too-many-branches
for uri in self.dependencies:
for spec 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:
lm = LibraryPackageManager(storage_dir)
pkg = lm.get_package(spec)
if not pkg:
continue
for lb in self.env.GetLibBuilders():
if lib_dir != lb.path:
if pkg.path != lb.path:
continue
if lb not in self.depbuilders:
self.depend_recursive(lb)
@ -908,7 +912,7 @@ class ProjectAsLibBuilder(LibBuilderBase):
# look for built-in libraries by a name
# which don't have package manifest
for lb in self.env.GetLibBuilders():
if lb.name != uri:
if lb.name != spec:
continue
if lb not in self.depbuilders:
self.depend_recursive(lb)
@ -1000,10 +1004,6 @@ def GetLibBuilders(env): # pylint: disable=too-many-branches
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]
@ -1019,15 +1019,13 @@ def ConfigureProjectLibBuilder(env):
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")
pkg = PackageSourceItem(lb.path)
if pkg.metadata:
title += " %s" % pkg.metadata.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)
if pkg.metadata and pkg.metadata.spec.external:
click.echo(" [%s]" % pkg.metadata.spec.url, nl=False)
click.echo(" (", nl=False)
click.echo(lb.path, nl=False)
click.echo(")", nl=False)

View File

@ -30,7 +30,9 @@ class HTTPClientError(PlatformioException):
class HTTPClient(object):
def __init__(self, base_url):
def __init__(
self, base_url,
):
if base_url.endswith("/"):
base_url = base_url[:-1]
self.base_url = base_url
@ -51,6 +53,7 @@ class HTTPClient(object):
self._session.close()
self._session = None
@util.throttle(500)
def send_request(self, method, path, **kwargs):
# check Internet before and resolve issue with 60 seconds timeout
# print(self, method, path, kwargs)

View File

@ -0,0 +1,13 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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.

View File

@ -18,16 +18,21 @@ import os
import time
import click
import semantic_version
from tabulate import tabulate
from platformio import exception, fs, util
from platformio.commands import PlatformioCLI
from platformio.commands.lib.helpers import (
get_builtin_libs,
is_builtin_lib,
save_project_libdeps,
)
from platformio.compat import dump_json_to_unicode
from platformio.managers.lib import LibraryManager, get_builtin_libs, is_builtin_lib
from platformio.package.exception import UnknownPackageError
from platformio.package.manager.library import LibraryPackageManager
from platformio.package.meta import PackageSourceItem, PackageSpec
from platformio.proc import is_ci
from platformio.project.config import ProjectConfig
from platformio.project.exception import InvalidProjectConfError
from platformio.project.helpers import get_project_dir, is_platformio_project
try:
@ -124,89 +129,106 @@ def cli(ctx, **options):
@cli.command("install", short_help="Install library")
@click.argument("libraries", required=False, nargs=-1, metavar="[LIBRARY...]")
@click.option(
"--save",
"--save/--no-save",
is_flag=True,
help="Save installed libraries into the `platformio.ini` dependency list",
default=True,
help="Save installed libraries into the `platformio.ini` dependency list"
" (enabled by default)",
)
@click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting")
@click.option(
"--interactive", is_flag=True, help="Allow to make a choice for all prompts"
"--interactive",
is_flag=True,
help="Deprecated! Please use a strict dependency specification (owner/libname)",
)
@click.option(
"-f", "--force", is_flag=True, help="Reinstall/redownload library if exists"
)
@click.pass_context
def lib_install( # pylint: disable=too-many-arguments
def lib_install( # pylint: disable=too-many-arguments,unused-argument
ctx, libraries, save, silent, interactive, force
):
storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY]
storage_libdeps = ctx.meta.get(CTX_META_STORAGE_LIBDEPS_KEY, [])
installed_manifests = {}
installed_pkgs = {}
for storage_dir in storage_dirs:
if not silent and (libraries or storage_dir in storage_libdeps):
print_storage_header(storage_dirs, storage_dir)
lm = LibraryManager(storage_dir)
lm = LibraryPackageManager(storage_dir)
if libraries:
for library in libraries:
pkg_dir = lm.install(
library, silent=silent, interactive=interactive, force=force
)
installed_manifests[library] = lm.load_manifest(pkg_dir)
installed_pkgs = {
library: lm.install(library, silent=silent, force=force)
for library in libraries
}
elif storage_dir in storage_libdeps:
builtin_lib_storages = None
for library in storage_libdeps[storage_dir]:
try:
pkg_dir = lm.install(
library, silent=silent, interactive=interactive, force=force
)
installed_manifests[library] = lm.load_manifest(pkg_dir)
except exception.LibNotFound as e:
lm.install(library, silent=silent, force=force)
except UnknownPackageError as e:
if builtin_lib_storages is None:
builtin_lib_storages = get_builtin_libs()
if not silent or not is_builtin_lib(builtin_lib_storages, library):
click.secho("Warning! %s" % e, fg="yellow")
if not save or not libraries:
return
if save and installed_pkgs:
_save_deps(ctx, installed_pkgs)
def _save_deps(ctx, pkgs, action="add"):
specs = []
for library, pkg in pkgs.items():
spec = PackageSpec(library)
if spec.external:
specs.append(spec)
else:
specs.append(
PackageSpec(
owner=pkg.metadata.spec.owner,
name=pkg.metadata.spec.name,
requirements=spec.requirements
or (
("^%s" % pkg.metadata.version)
if not pkg.metadata.version.build
else pkg.metadata.version
),
)
)
input_dirs = ctx.meta.get(CTX_META_INPUT_DIRS_KEY, [])
project_environments = ctx.meta[CTX_META_PROJECT_ENVIRONMENTS_KEY]
for input_dir in input_dirs:
config = ProjectConfig.get_instance(os.path.join(input_dir, "platformio.ini"))
config.validate(project_environments)
for env in config.envs():
if project_environments and env not in project_environments:
continue
config.expand_interpolations = False
try:
lib_deps = config.get("env:" + env, "lib_deps")
except InvalidProjectConfError:
lib_deps = []
for library in libraries:
if library in lib_deps:
continue
manifest = installed_manifests[library]
try:
assert library.lower() == manifest["name"].lower()
assert semantic_version.Version(manifest["version"])
lib_deps.append("{name}@^{version}".format(**manifest))
except (AssertionError, ValueError):
lib_deps.append(library)
config.set("env:" + env, "lib_deps", lib_deps)
config.save()
if not is_platformio_project(input_dir):
continue
save_project_libdeps(input_dir, specs, project_environments, action=action)
@cli.command("uninstall", short_help="Uninstall libraries")
@cli.command("uninstall", short_help="Remove libraries")
@click.argument("libraries", nargs=-1, metavar="[LIBRARY...]")
@click.option(
"--save/--no-save",
is_flag=True,
default=True,
help="Remove libraries from the `platformio.ini` dependency list and save changes"
" (enabled by default)",
)
@click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting")
@click.pass_context
def lib_uninstall(ctx, libraries):
def lib_uninstall(ctx, libraries, save, silent):
storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY]
uninstalled_pkgs = {}
for storage_dir in storage_dirs:
print_storage_header(storage_dirs, storage_dir)
lm = LibraryManager(storage_dir)
for library in libraries:
lm.uninstall(library)
lm = LibraryPackageManager(storage_dir)
uninstalled_pkgs = {
library: lm.uninstall(library, silent=silent) for library in libraries
}
if save and uninstalled_pkgs:
_save_deps(ctx, uninstalled_pkgs, action="remove")
@cli.command("update", short_help="Update installed libraries")
@ -220,42 +242,53 @@ def lib_uninstall(ctx, libraries):
@click.option(
"--dry-run", is_flag=True, help="Do not update, only check for the new versions"
)
@click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting")
@click.option("--json-output", is_flag=True)
@click.pass_context
def lib_update(ctx, libraries, only_check, dry_run, json_output):
def lib_update( # pylint: disable=too-many-arguments
ctx, libraries, only_check, dry_run, silent, json_output
):
storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY]
only_check = dry_run or only_check
json_result = {}
for storage_dir in storage_dirs:
if not json_output:
print_storage_header(storage_dirs, storage_dir)
lm = LibraryManager(storage_dir)
_libraries = libraries
if not _libraries:
_libraries = [manifest["__pkg_dir"] for manifest in lm.get_installed()]
lm = LibraryPackageManager(storage_dir)
_libraries = libraries or lm.get_installed()
if only_check and json_output:
result = []
for library in _libraries:
pkg_dir = library if os.path.isdir(library) else None
requirements = None
url = None
if not pkg_dir:
name, requirements, url = lm.parse_pkg_uri(library)
pkg_dir = lm.get_package_dir(name, requirements, url)
if not pkg_dir:
spec = None
pkg = None
if isinstance(library, PackageSourceItem):
pkg = library
else:
spec = PackageSpec(library)
pkg = lm.get_package(spec)
if not pkg:
continue
latest = lm.outdated(pkg_dir, requirements)
if not latest:
outdated = lm.outdated(pkg, spec)
if not outdated.is_outdated(allow_incompatible=True):
continue
manifest = lm.load_manifest(pkg_dir)
manifest["versionLatest"] = latest
manifest = lm.legacy_load_manifest(pkg)
manifest["versionWanted"] = (
str(outdated.wanted) if outdated.wanted else None
)
manifest["versionLatest"] = (
str(outdated.latest) if outdated.latest else None
)
result.append(manifest)
json_result[storage_dir] = result
else:
for library in _libraries:
lm.update(library, only_check=only_check)
spec = (
None
if isinstance(library, PackageSourceItem)
else PackageSpec(library)
)
lm.update(library, spec=spec, only_check=only_check, silent=silent)
if json_output:
return click.echo(
@ -276,8 +309,8 @@ def lib_list(ctx, json_output):
for storage_dir in storage_dirs:
if not json_output:
print_storage_header(storage_dirs, storage_dir)
lm = LibraryManager(storage_dir)
items = lm.get_installed()
lm = LibraryPackageManager(storage_dir)
items = lm.legacy_get_installed()
if json_output:
json_result[storage_dir] = items
elif items:
@ -301,6 +334,7 @@ def lib_list(ctx, json_output):
@click.option("--json-output", is_flag=True)
@click.option("--page", type=click.INT, default=1)
@click.option("--id", multiple=True)
@click.option("-o", "--owner", multiple=True)
@click.option("-n", "--name", multiple=True)
@click.option("-a", "--author", multiple=True)
@click.option("-k", "--keyword", multiple=True)
@ -404,12 +438,8 @@ def lib_builtin(storage, json_output):
@click.argument("library", metavar="[LIBRARY]")
@click.option("--json-output", is_flag=True)
def lib_show(library, json_output):
lm = LibraryManager()
name, requirements, _ = lm.parse_pkg_uri(library)
lib_id = lm.search_lib_id(
{"name": name, "requirements": requirements},
silent=json_output,
interactive=not json_output,
lib_id = LibraryPackageManager().reveal_registry_package_id(
library, silent=json_output
)
lib = util.get_api_result("/lib/info/%d" % lib_id, cache_valid="1d")
if json_output:

View File

@ -0,0 +1,90 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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.
import os
from platformio.compat import ci_strings_are_equal
from platformio.managers.platform import PlatformFactory, PlatformManager
from platformio.package.meta import PackageSpec
from platformio.project.config import ProjectConfig
from platformio.project.exception import InvalidProjectConfError
def get_builtin_libs(storage_names=None):
# pylint: disable=import-outside-toplevel
from platformio.package.manager.library import LibraryPackageManager
items = []
storage_names = storage_names or []
pm = PlatformManager()
for manifest in pm.get_installed():
p = PlatformFactory.newPlatform(manifest["__pkg_dir"])
for storage in p.get_lib_storages():
if storage_names and storage["name"] not in storage_names:
continue
lm = LibraryPackageManager(storage["path"])
items.append(
{
"name": storage["name"],
"path": storage["path"],
"items": lm.legacy_get_installed(),
}
)
return items
def is_builtin_lib(storages, name):
for storage in storages or []:
if any(lib.get("name") == name for lib in storage["items"]):
return True
return False
def ignore_deps_by_specs(deps, specs):
result = []
for dep in deps:
depspec = PackageSpec(dep)
if depspec.external:
result.append(dep)
continue
ignore_conditions = []
for spec in specs:
if depspec.owner:
ignore_conditions.append(
ci_strings_are_equal(depspec.owner, spec.owner)
and ci_strings_are_equal(depspec.name, spec.name)
)
else:
ignore_conditions.append(ci_strings_are_equal(depspec.name, spec.name))
if not any(ignore_conditions):
result.append(dep)
return result
def save_project_libdeps(project_dir, specs, environments=None, action="add"):
config = ProjectConfig.get_instance(os.path.join(project_dir, "platformio.ini"))
config.validate(environments)
for env in config.envs():
if environments and env not in environments:
continue
config.expand_interpolations = False
lib_deps = []
try:
lib_deps = ignore_deps_by_specs(config.get("env:" + env, "lib_deps"), specs)
except InvalidProjectConfError:
pass
if action == "add":
lib_deps.extend(spec.as_dependency() for spec in specs)
config.set("env:" + env, "lib_deps", lib_deps)
config.save()

View File

@ -26,9 +26,9 @@ from platformio.commands.system.completion import (
install_completion_code,
uninstall_completion_code,
)
from platformio.managers.lib import LibraryManager
from platformio.managers.package import PackageManager
from platformio.managers.platform import PlatformManager
from platformio.package.manager.library import LibraryPackageManager
from platformio.project.config import ProjectConfig
@ -73,7 +73,7 @@ def system_info(json_output):
}
data["global_lib_nums"] = {
"title": "Global Libraries",
"value": len(LibraryManager().get_installed()),
"value": len(LibraryPackageManager().get_installed()),
}
data["dev_platform_nums"] = {
"title": "Development Platforms",

View File

@ -15,11 +15,11 @@
import click
from platformio import app
from platformio.commands.lib import CTX_META_STORAGE_DIRS_KEY
from platformio.commands.lib import lib_update as cmd_lib_update
from platformio.commands.lib.command import CTX_META_STORAGE_DIRS_KEY
from platformio.commands.lib.command import lib_update as cmd_lib_update
from platformio.commands.platform import platform_update as cmd_platform_update
from platformio.managers.core import update_core_packages
from platformio.managers.lib import LibraryManager
from platformio.package.manager.library import LibraryPackageManager
@click.command(
@ -55,5 +55,5 @@ def cli(ctx, core_packages, only_check, dry_run):
click.echo()
click.echo("Library Manager")
click.echo("===============")
ctx.meta[CTX_META_STORAGE_DIRS_KEY] = [LibraryManager().package_dir]
ctx.meta[CTX_META_STORAGE_DIRS_KEY] = [LibraryPackageManager().package_dir]
ctx.invoke(cmd_lib_update, only_check=only_check)

View File

@ -50,6 +50,14 @@ def get_object_members(obj, ignore_private=True):
}
def ci_strings_are_equal(a, b):
if a == b:
return True
if not a or not b:
return False
return a.strip().lower() == b.strip().lower()
if PY2:
import imp

View File

@ -124,15 +124,6 @@ class PackageInstallError(PlatformIOPackageException):
#
class LibNotFound(PlatformioException):
MESSAGE = (
"Library `{0}` has not been found in PlatformIO Registry.\n"
"You can ignore this message, if `{0}` is a built-in library "
"(included in framework, SDK). E.g., SPI, Wire, etc."
)
class NotGlobalLibDir(UserSideException):
MESSAGE = (

View File

@ -21,13 +21,13 @@ import semantic_version
from platformio import __version__, app, exception, fs, telemetry, util
from platformio.commands import PlatformioCLI
from platformio.commands.lib import CTX_META_STORAGE_DIRS_KEY
from platformio.commands.lib import lib_update as cmd_lib_update
from platformio.commands.lib.command import CTX_META_STORAGE_DIRS_KEY
from platformio.commands.lib.command import lib_update as cmd_lib_update
from platformio.commands.platform import platform_update as cmd_platform_update
from platformio.commands.upgrade import get_latest_version
from platformio.managers.core import update_core_packages
from platformio.managers.lib import LibraryManager
from platformio.managers.platform import PlatformFactory, PlatformManager
from platformio.package.manager.library import LibraryPackageManager
from platformio.proc import is_container
@ -240,7 +240,7 @@ def check_platformio_upgrade():
click.echo("")
def check_internal_updates(ctx, what):
def check_internal_updates(ctx, what): # pylint: disable=too-many-branches
last_check = app.get_state_item("last_check", {})
interval = int(app.get_setting("check_%s_interval" % what)) * 3600 * 24
if (time() - interval) < last_check.get(what + "_update", 0):
@ -251,20 +251,27 @@ def check_internal_updates(ctx, what):
util.internet_on(raise_exception=True)
pm = PlatformManager() if what == "platforms" else LibraryManager()
outdated_items = []
for manifest in pm.get_installed():
if manifest["name"] in outdated_items:
continue
conds = [
pm.outdated(manifest["__pkg_dir"]),
what == "platforms"
and PlatformFactory.newPlatform(
manifest["__pkg_dir"]
).are_outdated_packages(),
]
if any(conds):
outdated_items.append(manifest["name"])
pm = PlatformManager() if what == "platforms" else LibraryPackageManager()
if isinstance(pm, PlatformManager):
for manifest in pm.get_installed():
if manifest["name"] in outdated_items:
continue
conds = [
pm.outdated(manifest["__pkg_dir"]),
what == "platforms"
and PlatformFactory.newPlatform(
manifest["__pkg_dir"]
).are_outdated_packages(),
]
if any(conds):
outdated_items.append(manifest["name"])
else:
for pkg in pm.get_installed():
if pkg.metadata.name in outdated_items:
continue
if pm.outdated(pkg).is_outdated():
outdated_items.append(pkg.metadata.name)
if not outdated_items:
return

View File

@ -1,374 +0,0 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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=too-many-arguments, too-many-locals, too-many-branches
# pylint: disable=too-many-return-statements
import json
from glob import glob
from os.path import isdir, join
import click
import semantic_version
from platformio import app, exception, util
from platformio.compat import glob_escape
from platformio.managers.package import BasePkgManager
from platformio.managers.platform import PlatformFactory, PlatformManager
from platformio.package.exception import ManifestException
from platformio.package.manifest.parser import ManifestParserFactory
from platformio.project.config import ProjectConfig
class LibraryManager(BasePkgManager):
FILE_CACHE_VALID = "30d" # 1 month
def __init__(self, package_dir=None):
self.config = ProjectConfig.get_instance()
super(LibraryManager, self).__init__(
package_dir or self.config.get_optional_dir("globallib")
)
@property
def manifest_names(self):
return [".library.json", "library.json", "library.properties", "module.json"]
def get_manifest_path(self, pkg_dir):
path = BasePkgManager.get_manifest_path(self, pkg_dir)
if path:
return path
# if library without manifest, returns first source file
src_dir = join(glob_escape(pkg_dir))
if isdir(join(pkg_dir, "src")):
src_dir = join(src_dir, "src")
chs_files = glob(join(src_dir, "*.[chS]"))
if chs_files:
return chs_files[0]
cpp_files = glob(join(src_dir, "*.cpp"))
if cpp_files:
return cpp_files[0]
return None
def max_satisfying_repo_version(self, versions, requirements=None):
def _cmp_dates(datestr1, datestr2):
date1 = util.parse_date(datestr1)
date2 = util.parse_date(datestr2)
if date1 == date2:
return 0
return -1 if date1 < date2 else 1
semver_spec = None
try:
semver_spec = (
semantic_version.SimpleSpec(requirements) if requirements else None
)
except ValueError:
pass
item = {}
for v in versions:
semver_new = self.parse_semver_version(v["name"])
if semver_spec:
# pylint: disable=unsupported-membership-test
if not semver_new or semver_new not in semver_spec:
continue
if not item or self.parse_semver_version(item["name"]) < semver_new:
item = v
elif requirements:
if requirements == v["name"]:
return v
else:
if not item or _cmp_dates(item["released"], v["released"]) == -1:
item = v
return item
def get_latest_repo_version(self, name, requirements, silent=False):
item = self.max_satisfying_repo_version(
util.get_api_result(
"/lib/info/%d"
% self.search_lib_id(
{"name": name, "requirements": requirements}, silent=silent
),
cache_valid="1h",
)["versions"],
requirements,
)
return item["name"] if item else None
def _install_from_piorepo(self, name, requirements):
assert name.startswith("id="), name
version = self.get_latest_repo_version(name, requirements)
if not version:
raise exception.UndefinedPackageVersion(
requirements or "latest", util.get_systype()
)
dl_data = util.get_api_result(
"/lib/download/" + str(name[3:]), dict(version=version), cache_valid="30d"
)
assert dl_data
return self._install_from_url(
name,
dl_data["url"].replace("http://", "https://")
if app.get_setting("strict_ssl")
else dl_data["url"],
requirements,
)
def search_lib_id( # pylint: disable=too-many-branches
self, filters, silent=False, interactive=False
):
assert isinstance(filters, dict)
assert "name" in filters
# try to find ID within installed packages
lib_id = self._get_lib_id_from_installed(filters)
if lib_id:
return lib_id
# looking in PIO Library Registry
if not silent:
click.echo(
"Looking for %s library in registry"
% click.style(filters["name"], fg="cyan")
)
query = []
for key in filters:
if key not in ("name", "authors", "frameworks", "platforms"):
continue
values = filters[key]
if not isinstance(values, list):
values = [v.strip() for v in values.split(",") if v]
for value in values:
query.append(
'%s:"%s"' % (key[:-1] if key.endswith("s") else key, value)
)
lib_info = None
result = util.get_api_result(
"/v2/lib/search", dict(query=" ".join(query)), cache_valid="1h"
)
if result["total"] == 1:
lib_info = result["items"][0]
elif result["total"] > 1:
if silent and not interactive:
lib_info = result["items"][0]
else:
click.secho(
"Conflict: More than one library has been found "
"by request %s:" % json.dumps(filters),
fg="yellow",
err=True,
)
# pylint: disable=import-outside-toplevel
from platformio.commands.lib import print_lib_item
for item in result["items"]:
print_lib_item(item)
if not interactive:
click.secho(
"Automatically chose the first available library "
"(use `--interactive` option to make a choice)",
fg="yellow",
err=True,
)
lib_info = result["items"][0]
else:
deplib_id = click.prompt(
"Please choose library ID",
type=click.Choice([str(i["id"]) for i in result["items"]]),
)
for item in result["items"]:
if item["id"] == int(deplib_id):
lib_info = item
break
if not lib_info:
if list(filters) == ["name"]:
raise exception.LibNotFound(filters["name"])
raise exception.LibNotFound(str(filters))
if not silent:
click.echo(
"Found: %s"
% click.style(
"https://platformio.org/lib/show/{id}/{name}".format(**lib_info),
fg="blue",
)
)
return int(lib_info["id"])
def _get_lib_id_from_installed(self, filters):
if filters["name"].startswith("id="):
return int(filters["name"][3:])
package_dir = self.get_package_dir(
filters["name"], filters.get("requirements", filters.get("version"))
)
if not package_dir:
return None
manifest = self.load_manifest(package_dir)
if "id" not in manifest:
return None
for key in ("frameworks", "platforms"):
if key not in filters:
continue
if key not in manifest:
return None
if not util.items_in_list(
util.items_to_list(filters[key]), util.items_to_list(manifest[key])
):
return None
if "authors" in filters:
if "authors" not in manifest:
return None
manifest_authors = manifest["authors"]
if not isinstance(manifest_authors, list):
manifest_authors = [manifest_authors]
manifest_authors = [
a["name"]
for a in manifest_authors
if isinstance(a, dict) and "name" in a
]
filter_authors = filters["authors"]
if not isinstance(filter_authors, list):
filter_authors = [filter_authors]
if not set(filter_authors) <= set(manifest_authors):
return None
return int(manifest["id"])
def install( # pylint: disable=arguments-differ
self,
name,
requirements=None,
silent=False,
after_update=False,
interactive=False,
force=False,
):
_name, _requirements, _url = self.parse_pkg_uri(name, requirements)
if not _url:
name = "id=%d" % self.search_lib_id(
{"name": _name, "requirements": _requirements},
silent=silent,
interactive=interactive,
)
requirements = _requirements
pkg_dir = BasePkgManager.install(
self,
name,
requirements,
silent=silent,
after_update=after_update,
force=force,
)
if not pkg_dir:
return None
manifest = None
try:
manifest = ManifestParserFactory.new_from_dir(pkg_dir).as_dict()
except ManifestException:
pass
if not manifest or not manifest.get("dependencies"):
return pkg_dir
if not silent:
click.secho("Installing dependencies", fg="yellow")
builtin_lib_storages = None
for filters in manifest["dependencies"]:
assert "name" in filters
# avoid circle dependencies
if not self.INSTALL_HISTORY:
self.INSTALL_HISTORY = []
history_key = str(filters)
if history_key in self.INSTALL_HISTORY:
continue
self.INSTALL_HISTORY.append(history_key)
if any(s in filters.get("version", "") for s in ("\\", "/")):
self.install(
"{name}={version}".format(**filters),
silent=silent,
after_update=after_update,
interactive=interactive,
force=force,
)
else:
try:
lib_id = self.search_lib_id(filters, silent, interactive)
except exception.LibNotFound as e:
if builtin_lib_storages is None:
builtin_lib_storages = get_builtin_libs()
if not silent or is_builtin_lib(
builtin_lib_storages, filters["name"]
):
click.secho("Warning! %s" % e, fg="yellow")
continue
if filters.get("version"):
self.install(
lib_id,
filters.get("version"),
silent=silent,
after_update=after_update,
interactive=interactive,
force=force,
)
else:
self.install(
lib_id,
silent=silent,
after_update=after_update,
interactive=interactive,
force=force,
)
return pkg_dir
def get_builtin_libs(storage_names=None):
items = []
storage_names = storage_names or []
pm = PlatformManager()
for manifest in pm.get_installed():
p = PlatformFactory.newPlatform(manifest["__pkg_dir"])
for storage in p.get_lib_storages():
if storage_names and storage["name"] not in storage_names:
continue
lm = LibraryManager(storage["path"])
items.append(
{
"name": storage["name"],
"path": storage["path"],
"items": lm.get_installed(),
}
)
return items
def is_builtin_lib(storages, name):
for storage in storages or []:
if any(l.get("name") == name for l in storage["items"]):
return True
return False

View File

@ -482,7 +482,7 @@ class PkgInstallerMixin(object):
self.unpack(dlpath, tmp_dir)
os.remove(dlpath)
else:
vcs = VCSClientFactory.newClient(tmp_dir, url)
vcs = VCSClientFactory.new(tmp_dir, url)
assert vcs.export()
src_manifest_dir = vcs.storage_dir
src_manifest["version"] = vcs.get_current_revision()
@ -628,9 +628,7 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
if "__src_url" in manifest:
try:
vcs = VCSClientFactory.newClient(
pkg_dir, manifest["__src_url"], silent=True
)
vcs = VCSClientFactory.new(pkg_dir, manifest["__src_url"], silent=True)
except (AttributeError, exception.PlatformioException):
return None
if not vcs.can_be_updated:
@ -800,7 +798,7 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
return True
if "__src_url" in manifest:
vcs = VCSClientFactory.newClient(pkg_dir, manifest["__src_url"])
vcs = VCSClientFactory.new(pkg_dir, manifest["__src_url"])
assert vcs.update()
self._update_src_manifest(
dict(version=vcs.get_current_revision()), vcs.storage_dir

View File

@ -20,7 +20,7 @@ import tempfile
import click
from platformio import app, compat, fs, util
from platformio.package.exception import PackageException
from platformio.package.exception import MissingPackageManifestError, PackageException
from platformio.package.meta import PackageSourceItem, PackageSpec
from platformio.package.unpack import FileUnpacker
from platformio.package.vcsclient import VCSClientFactory
@ -83,7 +83,7 @@ class PackageManagerInstallMixin(object):
msg = "Installing %s" % click.style(spec.humanize(), fg="cyan")
self.print_message(msg)
if spec.url:
if spec.external:
pkg = self.install_from_url(spec.url, spec, silent=silent)
else:
pkg = self.install_from_registry(spec, search_filters, silent=silent)
@ -152,7 +152,7 @@ class PackageManagerInstallMixin(object):
assert os.path.isfile(dl_path)
self.unpack(dl_path, tmp_dir)
else:
vcs = VCSClientFactory.newClient(tmp_dir, url)
vcs = VCSClientFactory.new(tmp_dir, url)
assert vcs.export()
root_dir = self.find_pkg_root(tmp_dir, spec)
@ -189,12 +189,20 @@ class PackageManagerInstallMixin(object):
# what to do with existing package?
action = "overwrite"
if dst_pkg.metadata and dst_pkg.metadata.spec.url:
if tmp_pkg.metadata.spec.has_custom_name():
action = "overwrite"
dst_pkg = PackageSourceItem(
os.path.join(self.package_dir, tmp_pkg.metadata.spec.name)
)
elif dst_pkg.metadata and dst_pkg.metadata.spec.external:
if dst_pkg.metadata.spec.url != tmp_pkg.metadata.spec.url:
action = "detach-existing"
elif tmp_pkg.metadata.spec.url:
elif tmp_pkg.metadata.spec.external:
action = "detach-new"
elif dst_pkg.metadata and dst_pkg.metadata.version != tmp_pkg.metadata.version:
elif dst_pkg.metadata and (
dst_pkg.metadata.version != tmp_pkg.metadata.version
or dst_pkg.metadata.spec.owner != tmp_pkg.metadata.spec.owner
):
action = (
"detach-existing"
if tmp_pkg.metadata.version > dst_pkg.metadata.version
@ -231,7 +239,7 @@ class PackageManagerInstallMixin(object):
tmp_pkg.get_safe_dirname(),
tmp_pkg.metadata.version,
)
if tmp_pkg.metadata.spec.url:
if tmp_pkg.metadata.spec.external:
target_dirname = "%s@src-%s" % (
tmp_pkg.get_safe_dirname(),
hashlib.md5(
@ -247,3 +255,20 @@ class PackageManagerInstallMixin(object):
_cleanup_dir(dst_pkg.path)
shutil.move(tmp_pkg.path, dst_pkg.path)
return PackageSourceItem(dst_pkg.path)
def get_installed(self):
result = []
for name in os.listdir(self.package_dir):
pkg_dir = os.path.join(self.package_dir, name)
if not os.path.isdir(pkg_dir):
continue
pkg = PackageSourceItem(pkg_dir)
if not pkg.metadata:
try:
spec = self.build_legacy_spec(pkg_dir)
pkg.metadata = self.build_metadata(pkg_dir, spec)
except MissingPackageManifestError:
pass
if pkg.metadata:
result.append(pkg)
return result

View File

@ -0,0 +1,57 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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.
import os
from platformio import fs
from platformio.package.meta import PackageSourceItem, PackageSpec
class PackageManagerLegacyMixin(object):
def build_legacy_spec(self, pkg_dir):
# find src manifest
src_manifest_name = ".piopkgmanager.json"
src_manifest_path = None
for name in os.listdir(pkg_dir):
if not os.path.isfile(os.path.join(pkg_dir, name, src_manifest_name)):
continue
src_manifest_path = os.path.join(pkg_dir, name, src_manifest_name)
break
if src_manifest_path:
src_manifest = fs.load_json(src_manifest_path)
return PackageSpec(
name=src_manifest.get("name"),
url=src_manifest.get("url"),
requirements=src_manifest.get("requirements"),
)
# fall back to a package manifest
manifest = self.load_manifest(pkg_dir)
return PackageSpec(name=manifest.get("name"))
def legacy_load_manifest(self, pkg):
assert isinstance(pkg, PackageSourceItem)
manifest = self.load_manifest(pkg)
manifest["__pkg_dir"] = pkg.path
for key in ("name", "version"):
if not manifest.get(key):
manifest[key] = str(getattr(pkg.metadata, key))
if pkg.metadata and pkg.metadata.spec and pkg.metadata.spec.external:
manifest["__src_url"] = pkg.metadata.spec.url
manifest["version"] = str(pkg.metadata.version)
return manifest
def legacy_get_installed(self):
return [self.legacy_load_manifest(pkg) for pkg in self.get_installed()]

View File

@ -79,10 +79,10 @@ class RegistryFileMirrorsIterator(object):
class PackageManageRegistryMixin(object):
def install_from_registry(self, spec, search_filters=None, silent=False):
if spec.owner and spec.name and not search_filters:
package = self.fetch_registry_package(spec.owner, spec.name)
package = self.fetch_registry_package(spec)
if not package:
raise UnknownPackageError(spec.humanize())
version = self._pick_best_pkg_version(package["versions"], spec)
version = self.pick_best_registry_version(package["versions"], spec)
else:
packages = self.search_registry_packages(spec, search_filters)
if not packages:
@ -131,10 +131,33 @@ class PackageManageRegistryMixin(object):
"items"
]
def fetch_registry_package(self, owner, name):
return self.get_registry_client_instance().get_package(
self.pkg_type, owner, name
)
def fetch_registry_package(self, spec):
result = None
if spec.owner and spec.name:
result = self.get_registry_client_instance().get_package(
self.pkg_type, spec.owner, spec.name
)
if not result and (spec.id or (spec.name and not spec.owner)):
packages = self.search_registry_packages(spec)
if packages:
result = self.get_registry_client_instance().get_package(
self.pkg_type, packages[0]["owner"]["username"], packages[0]["name"]
)
if not result:
raise UnknownPackageError(spec.humanize())
return result
def reveal_registry_package_id(self, spec, silent=False):
spec = self.ensure_spec(spec)
if spec.id:
return spec.id
packages = self.search_registry_packages(spec)
if not packages:
raise UnknownPackageError(spec.humanize())
if len(packages) > 1 and not silent:
self.print_multi_package_issue(packages, spec)
click.echo("")
return packages[0]["id"]
@staticmethod
def print_multi_package_issue(packages, spec):
@ -160,7 +183,7 @@ class PackageManageRegistryMixin(object):
def find_best_registry_version(self, packages, spec):
# find compatible version within the latest package versions
for package in packages:
version = self._pick_best_pkg_version([package["version"]], spec)
version = self.pick_best_registry_version([package["version"]], spec)
if version:
return (package, version)
@ -169,9 +192,13 @@ class PackageManageRegistryMixin(object):
# if the custom version requirements, check ALL package versions
for package in packages:
version = self._pick_best_pkg_version(
version = self.pick_best_registry_version(
self.fetch_registry_package(
package["owner"]["username"], package["name"]
PackageSpec(
id=package["id"],
owner=package["owner"]["username"],
name=package["name"],
)
).get("versions"),
spec,
)
@ -180,11 +207,12 @@ class PackageManageRegistryMixin(object):
time.sleep(1)
return None
def _pick_best_pkg_version(self, versions, spec):
def pick_best_registry_version(self, versions, spec=None):
assert not spec or isinstance(spec, PackageSpec)
best = None
for version in versions:
semver = PackageMetaData.to_semver(version["name"])
if spec.requirements and semver not in spec.requirements:
if spec and spec.requirements and semver not in spec.requirements:
continue
if not any(
self.is_system_compatible(f.get("system")) for f in version["files"]

View File

@ -31,10 +31,7 @@ class PackageManagerUninstallMixin(object):
self.unlock()
def _uninstall(self, pkg, silent=False, skip_dependencies=False):
if not isinstance(pkg, PackageSourceItem):
pkg = (
PackageSourceItem(pkg) if os.path.isdir(pkg) else self.get_package(pkg)
)
pkg = self.get_package(pkg)
if not pkg or not pkg.metadata:
raise UnknownPackageError(pkg)
@ -73,7 +70,7 @@ class PackageManagerUninstallMixin(object):
if not silent:
click.echo("[%s]" % click.style("OK", fg="green"))
return True
return pkg
def _uninstall_dependencies(self, pkg, silent=False):
assert isinstance(pkg, PackageSourceItem)

View File

@ -0,0 +1,166 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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.
import os
import click
from platformio import util
from platformio.package.exception import UnknownPackageError
from platformio.package.meta import (
PackageOutdatedResult,
PackageSourceItem,
PackageSpec,
)
from platformio.package.vcsclient import VCSBaseException, VCSClientFactory
class PackageManagerUpdateMixin(object):
def outdated(self, pkg, spec=None):
assert isinstance(pkg, PackageSourceItem)
assert not spec or isinstance(spec, PackageSpec)
assert os.path.isdir(pkg.path) and pkg.metadata
# skip detached package to a specific version
detached_conditions = [
"@" in pkg.path,
pkg.metadata.spec and not pkg.metadata.spec.external,
not spec,
]
if all(detached_conditions):
return PackageOutdatedResult(current=pkg.metadata.version, detached=True)
latest = None
wanted = None
if pkg.metadata.spec.external:
latest = self._fetch_vcs_latest_version(pkg)
else:
try:
reg_pkg = self.fetch_registry_package(pkg.metadata.spec)
latest = (
self.pick_best_registry_version(reg_pkg["versions"]) or {}
).get("name")
if spec:
wanted = (
self.pick_best_registry_version(reg_pkg["versions"], spec) or {}
).get("name")
if not wanted: # wrong library
latest = None
except UnknownPackageError:
pass
return PackageOutdatedResult(
current=pkg.metadata.version, latest=latest, wanted=wanted
)
def _fetch_vcs_latest_version(self, pkg):
vcs = None
try:
vcs = VCSClientFactory.new(pkg.path, pkg.metadata.spec.url, silent=True)
except VCSBaseException:
return None
if not vcs.can_be_updated:
return None
return str(
self.build_metadata(
pkg.path, pkg.metadata.spec, vcs_revision=vcs.get_latest_revision()
).version
)
def update(self, pkg, spec=None, only_check=False, silent=False):
pkg = self.get_package(pkg)
if not pkg or not pkg.metadata:
raise UnknownPackageError(pkg)
if not silent:
click.echo(
"{} {:<45} {:<30}".format(
"Checking" if only_check else "Updating",
click.style(pkg.metadata.spec.humanize(), fg="cyan"),
"%s (%s)" % (pkg.metadata.version, spec.requirements)
if spec and spec.requirements
else str(pkg.metadata.version),
),
nl=False,
)
if not util.internet_on():
if not silent:
click.echo("[%s]" % (click.style("Off-line", fg="yellow")))
return pkg
outdated = self.outdated(pkg, spec)
if not silent:
self.print_outdated_state(outdated)
up_to_date = any(
[
outdated.detached,
not outdated.latest,
outdated.latest and outdated.current == outdated.latest,
outdated.wanted and outdated.current == outdated.wanted,
]
)
if only_check or up_to_date:
return pkg
try:
self.lock()
return self._update(pkg, outdated, silent=silent)
finally:
self.unlock()
@staticmethod
def print_outdated_state(outdated):
if outdated.detached:
return click.echo("[%s]" % (click.style("Detached", fg="yellow")))
if not outdated.latest or outdated.current == outdated.latest:
return click.echo("[%s]" % (click.style("Up-to-date", fg="green")))
if outdated.wanted and outdated.current == outdated.wanted:
return click.echo(
"[%s]"
% (click.style("Incompatible (%s)" % outdated.latest, fg="yellow"))
)
return click.echo(
"[%s]" % (click.style(str(outdated.wanted or outdated.latest), fg="red"))
)
def _update(self, pkg, outdated, silent=False):
if pkg.metadata.spec.external:
vcs = VCSClientFactory.new(pkg.path, pkg.metadata.spec.url)
assert vcs.update()
pkg.metadata.version = self._fetch_vcs_latest_version(pkg)
pkg.dump_meta()
return pkg
new_pkg = self.install(
PackageSpec(
id=pkg.metadata.spec.id,
owner=pkg.metadata.spec.owner,
name=pkg.metadata.spec.name,
requirements=outdated.wanted or outdated.latest,
),
silent=silent,
)
if new_pkg:
old_pkg = self.get_package(
PackageSpec(
id=pkg.metadata.spec.id,
owner=pkg.metadata.spec.owner,
name=pkg.metadata.name,
requirements=pkg.metadata.version,
)
)
if old_pkg:
self.uninstall(old_pkg, silent=silent, skip_dependencies=True)
return new_pkg

View File

@ -18,14 +18,17 @@ from datetime import datetime
import click
import semantic_version
from platformio import fs, util
from platformio import util
from platformio.commands import PlatformioCLI
from platformio.compat import ci_strings_are_equal
from platformio.package.exception import ManifestException, MissingPackageManifestError
from platformio.package.lockfile import LockFile
from platformio.package.manager._download import PackageManagerDownloadMixin
from platformio.package.manager._install import PackageManagerInstallMixin
from platformio.package.manager._legacy import PackageManagerLegacyMixin
from platformio.package.manager._registry import PackageManageRegistryMixin
from platformio.package.manager._uninstall import PackageManagerUninstallMixin
from platformio.package.manager._update import PackageManagerUpdateMixin
from platformio.package.manifest.parser import ManifestParserFactory
from platformio.package.meta import (
PackageMetaData,
@ -41,6 +44,8 @@ class BasePackageManager( # pylint: disable=too-many-public-methods
PackageManageRegistryMixin,
PackageManagerInstallMixin,
PackageManagerUninstallMixin,
PackageManagerUpdateMixin,
PackageManagerLegacyMixin,
):
_MEMORY_CACHE = {}
@ -83,10 +88,6 @@ class BasePackageManager( # pylint: disable=too-many-public-methods
return True
return util.items_in_list(value, util.get_systype())
@staticmethod
def generate_rand_version():
return datetime.now().strftime("0.0.0+%Y%m%d%H%M%S")
@staticmethod
def ensure_dir_exists(path):
if not os.path.isdir(path):
@ -162,27 +163,9 @@ class BasePackageManager( # pylint: disable=too-many-public-methods
click.secho(str(e), fg="yellow")
raise MissingPackageManifestError(", ".join(self.manifest_names))
def build_legacy_spec(self, pkg_dir):
# find src manifest
src_manifest_name = ".piopkgmanager.json"
src_manifest_path = None
for name in os.listdir(pkg_dir):
if not os.path.isfile(os.path.join(pkg_dir, name, src_manifest_name)):
continue
src_manifest_path = os.path.join(pkg_dir, name, src_manifest_name)
break
if src_manifest_path:
src_manifest = fs.load_json(src_manifest_path)
return PackageSpec(
name=src_manifest.get("name"),
url=src_manifest.get("url"),
requirements=src_manifest.get("requirements"),
)
# fall back to a package manifest
manifest = self.load_manifest(pkg_dir)
return PackageSpec(name=manifest.get("name"))
@staticmethod
def generate_rand_version():
return datetime.now().strftime("0.0.0+%Y%m%d%H%M%S")
def build_metadata(self, pkg_dir, spec, vcs_revision=None):
manifest = self.load_manifest(pkg_dir)
@ -192,7 +175,7 @@ class BasePackageManager( # pylint: disable=too-many-public-methods
version=manifest.get("version"),
spec=spec,
)
if not metadata.name or spec.is_custom_name():
if not metadata.name or spec.has_custom_name():
metadata.name = spec.name
if vcs_revision:
metadata.version = "%s+sha.%s" % (
@ -203,42 +186,27 @@ class BasePackageManager( # pylint: disable=too-many-public-methods
metadata.version = self.generate_rand_version()
return metadata
def get_installed(self):
result = []
for name in os.listdir(self.package_dir):
pkg_dir = os.path.join(self.package_dir, name)
if not os.path.isdir(pkg_dir):
continue
pkg = PackageSourceItem(pkg_dir)
if not pkg.metadata:
try:
spec = self.build_legacy_spec(pkg_dir)
pkg.metadata = self.build_metadata(pkg_dir, spec)
except MissingPackageManifestError:
pass
if pkg.metadata:
result.append(pkg)
return result
def get_package(self, spec):
def _ci_strings_are_equal(a, b):
if a == b:
return True
if not a or not b:
return False
return a.strip().lower() == b.strip().lower()
if isinstance(spec, PackageSourceItem):
return spec
if not isinstance(spec, PackageSpec) and os.path.isdir(spec):
for pkg in self.get_installed():
if spec == pkg.path:
return pkg
return None
spec = self.ensure_spec(spec)
best = None
for pkg in self.get_installed():
skip_conditions = [
spec.owner
and not _ci_strings_are_equal(spec.owner, pkg.metadata.spec.owner),
spec.url and spec.url != pkg.metadata.spec.url,
and not ci_strings_are_equal(spec.owner, pkg.metadata.spec.owner),
spec.external and spec.url != pkg.metadata.spec.url,
spec.id and spec.id != pkg.metadata.spec.id,
not spec.id
and not spec.url
and not _ci_strings_are_equal(spec.name, pkg.metadata.name),
and not spec.external
and not ci_strings_are_equal(spec.name, pkg.metadata.name),
]
if any(skip_conditions):
continue

View File

@ -21,7 +21,7 @@ from platformio.package.meta import PackageSpec, PackageType
from platformio.project.helpers import get_project_global_lib_dir
class LibraryPackageManager(BasePackageManager):
class LibraryPackageManager(BasePackageManager): # pylint: disable=too-many-ancestors
def __init__(self, package_dir=None):
super(LibraryPackageManager, self).__init__(
PackageType.LIBRARY, package_dir or get_project_global_lib_dir()

View File

@ -17,7 +17,7 @@ from platformio.package.meta import PackageType
from platformio.project.config import ProjectConfig
class PlatformPackageManager(BasePackageManager):
class PlatformPackageManager(BasePackageManager): # pylint: disable=too-many-ancestors
def __init__(self, package_dir=None):
self.config = ProjectConfig.get_instance()
super(PlatformPackageManager, self).__init__(

View File

@ -17,7 +17,7 @@ from platformio.package.meta import PackageType
from platformio.project.config import ProjectConfig
class ToolPackageManager(BasePackageManager):
class ToolPackageManager(BasePackageManager): # pylint: disable=too-many-ancestors
def __init__(self, package_dir=None):
self.config = ProjectConfig.get_instance()
super(ToolPackageManager, self).__init__(

View File

@ -250,7 +250,7 @@ class ManifestSchema(BaseSchema):
def load_spdx_licenses():
r = requests.get(
"https://raw.githubusercontent.com/spdx/license-list-data"
"/v3.9/json/licenses.json"
"/v3.10/json/licenses.json"
)
r.raise_for_status()
return r.json()

View File

@ -65,7 +65,44 @@ class PackageType(object):
return None
class PackageSpec(object):
class PackageOutdatedResult(object):
def __init__(self, current, latest=None, wanted=None, detached=False):
self.current = current
self.latest = latest
self.wanted = wanted
self.detached = detached
def __repr__(self):
return (
"PackageOutdatedResult <current={current} latest={latest} wanted={wanted} "
"detached={detached}>".format(
current=self.current,
latest=self.latest,
wanted=self.wanted,
detached=self.detached,
)
)
def __setattr__(self, name, value):
if (
value
and name in ("current", "latest", "wanted")
and not isinstance(value, semantic_version.Version)
):
value = semantic_version.Version(str(value))
return super(PackageOutdatedResult, self).__setattr__(name, value)
def is_outdated(self, allow_incompatible=False):
if self.detached or not self.latest or self.current == self.latest:
return False
if allow_incompatible:
return self.current != self.latest
if self.wanted:
return self.current != self.wanted
return True
class PackageSpec(object): # pylint: disable=too-many-instance-attributes
def __init__( # pylint: disable=redefined-builtin,too-many-arguments
self, raw=None, owner=None, id=None, name=None, requirements=None, url=None
):
@ -74,6 +111,7 @@ class PackageSpec(object):
self.name = name
self._requirements = None
self.url = url
self.raw = raw
if requirements:
self.requirements = requirements
self._name_is_custom = False
@ -104,6 +142,10 @@ class PackageSpec(object):
"requirements={requirements} url={url}>".format(**self.as_dict())
)
@property
def external(self):
return bool(self.url)
@property
def requirements(self):
return self._requirements
@ -116,24 +158,24 @@ class PackageSpec(object):
self._requirements = (
value
if isinstance(value, semantic_version.SimpleSpec)
else semantic_version.SimpleSpec(value)
else semantic_version.SimpleSpec(str(value))
)
def humanize(self):
result = ""
if self.url:
result = self.url
elif self.id:
result = "id:%d" % self.id
else:
result = ""
elif self.name:
if self.owner:
result = self.owner + "/"
result += self.name
elif self.id:
result = "id:%d" % self.id
if self.requirements:
result += " @ " + str(self.requirements)
return result
def is_custom_name(self):
def has_custom_name(self):
return self._name_is_custom
def as_dict(self):
@ -145,6 +187,19 @@ class PackageSpec(object):
url=self.url,
)
def as_dependency(self):
if self.url:
return self.raw or self.url
result = ""
if self.name:
result = "%s/%s" % (self.owner, self.name) if self.owner else self.name
elif self.id:
result = str(self.id)
assert result
if self.requirements:
result = "%s@%s" % (result, self.requirements)
return result
def _parse(self, raw):
if raw is None:
return

View File

@ -17,7 +17,11 @@ from os.path import join
from subprocess import CalledProcessError, check_call
from sys import modules
from platformio.exception import PlatformioException, UserSideException
from platformio.package.exception import (
PackageException,
PlatformioException,
UserSideException,
)
from platformio.proc import exec_command
try:
@ -26,9 +30,13 @@ except ImportError:
from urlparse import urlparse
class VCSBaseException(PackageException):
pass
class VCSClientFactory(object):
@staticmethod
def newClient(src_dir, remote_url, silent=False):
def new(src_dir, remote_url, silent=False):
result = urlparse(remote_url)
type_ = result.scheme
tag = None
@ -41,12 +49,15 @@ class VCSClientFactory(object):
if "#" in remote_url:
remote_url, tag = remote_url.rsplit("#", 1)
if not type_:
raise PlatformioException("VCS: Unknown repository type %s" % remote_url)
obj = getattr(modules[__name__], "%sClient" % type_.title())(
src_dir, remote_url, tag, silent
)
assert isinstance(obj, VCSClientBase)
return obj
raise VCSBaseException("VCS: Unknown repository type %s" % remote_url)
try:
obj = getattr(modules[__name__], "%sClient" % type_.title())(
src_dir, remote_url, tag, silent
)
assert isinstance(obj, VCSClientBase)
return obj
except (AttributeError, AssertionError):
raise VCSBaseException("VCS: Unknown repository type %s" % remote_url)
class VCSClientBase(object):
@ -101,7 +112,7 @@ class VCSClientBase(object):
check_call(args, **kwargs)
return True
except CalledProcessError as e:
raise PlatformioException("VCS: Could not process command %s" % e.cmd)
raise VCSBaseException("VCS: Could not process command %s" % e.cmd)
def get_cmd_output(self, args, **kwargs):
args = [self.command] + args
@ -110,7 +121,7 @@ class VCSClientBase(object):
result = exec_command(args, **kwargs)
if result["returncode"] == 0:
return result["out"].strip()
raise PlatformioException(
raise VCSBaseException(
"VCS: Could not receive an output from `%s` command (%s)" % (args, result)
)
@ -227,7 +238,6 @@ class SvnClient(VCSClientBase):
return self.run_cmd(args)
def update(self):
args = ["update"]
return self.run_cmd(args)
@ -239,4 +249,4 @@ class SvnClient(VCSClientBase):
line = line.strip()
if line.startswith("Revision:"):
return line.split(":", 1)[1].strip()
raise PlatformioException("Could not detect current SVN revision")
raise VCSBaseException("Could not detect current SVN revision")

View File

@ -15,7 +15,7 @@
from os.path import isfile, join
from platformio.commands.ci import cli as cmd_ci
from platformio.commands.lib import cli as cmd_lib
from platformio.commands.lib.command import cli as cmd_lib
def test_ci_empty(clirunner):

View File

@ -13,332 +13,184 @@
# limitations under the License.
import json
import re
import os
from platformio import exception
from platformio.commands import PlatformioCLI
from platformio.commands.lib import cli as cmd_lib
import semantic_version
PlatformioCLI.leftover_args = ["--json-output"] # hook for click
from platformio.clients.registry import RegistryClient
from platformio.commands.lib.command import cli as cmd_lib
from platformio.package.meta import PackageType
from platformio.package.vcsclient import VCSClientFactory
from platformio.project.config import ProjectConfig
def test_search(clirunner, validate_cliresult):
result = clirunner.invoke(cmd_lib, ["search", "DHT22"])
def test_saving_deps(clirunner, validate_cliresult, isolated_pio_core, tmpdir_factory):
regclient = RegistryClient()
project_dir = tmpdir_factory.mktemp("project")
project_dir.join("platformio.ini").write(
"""
[env]
lib_deps = ArduinoJson
[env:one]
board = devkit
[env:two]
framework = foo
lib_deps =
CustomLib
ArduinoJson @ 5.10.1
"""
)
result = clirunner.invoke(cmd_lib, ["-d", str(project_dir), "install", "64"])
validate_cliresult(result)
match = re.search(r"Found\s+(\d+)\slibraries:", result.output)
assert int(match.group(1)) > 2
aj_pkg_data = regclient.get_package(PackageType.LIBRARY, "bblanchon", "ArduinoJson")
config = ProjectConfig(os.path.join(str(project_dir), "platformio.ini"))
assert config.get("env:one", "lib_deps") == [
"bblanchon/ArduinoJson@^%s" % aj_pkg_data["version"]["name"]
]
assert config.get("env:two", "lib_deps") == [
"CustomLib",
"bblanchon/ArduinoJson@^%s" % aj_pkg_data["version"]["name"],
]
result = clirunner.invoke(cmd_lib, ["search", "DHT22", "--platform=timsp430"])
# ensure "build" version without NPM spec
result = clirunner.invoke(
cmd_lib,
["-d", str(project_dir), "-e", "one", "install", "mbed-sam-grove/LinkedList"],
)
validate_cliresult(result)
match = re.search(r"Found\s+(\d+)\slibraries:", result.output)
assert int(match.group(1)) > 1
ll_pkg_data = regclient.get_package(
PackageType.LIBRARY, "mbed-sam-grove", "LinkedList"
)
config = ProjectConfig(os.path.join(str(project_dir), "platformio.ini"))
assert config.get("env:one", "lib_deps") == [
"bblanchon/ArduinoJson@^%s" % aj_pkg_data["version"]["name"],
"mbed-sam-grove/LinkedList@%s" % ll_pkg_data["version"]["name"],
]
def test_global_install_registry(clirunner, validate_cliresult, isolated_pio_core):
# check external package via Git repo
result = clirunner.invoke(
cmd_lib,
[
"-g",
"-d",
str(project_dir),
"-e",
"one",
"install",
"64",
"ArduinoJson@~5.10.0",
"547@2.2.4",
"AsyncMqttClient@<=0.8.2",
"Adafruit PN532@1.2.0",
"https://github.com/OttoWinter/async-mqtt-client.git#v0.8.3 @ 0.8.3",
],
)
validate_cliresult(result)
config = ProjectConfig(os.path.join(str(project_dir), "platformio.ini"))
assert len(config.get("env:one", "lib_deps")) == 3
assert config.get("env:one", "lib_deps")[2] == (
"https://github.com/OttoWinter/async-mqtt-client.git#v0.8.3 @ 0.8.3"
)
# install unknown library
result = clirunner.invoke(cmd_lib, ["-g", "install", "Unknown"])
assert result.exit_code != 0
assert isinstance(result.exception, exception.LibNotFound)
items1 = [d.basename for d in isolated_pio_core.join("lib").listdir()]
items2 = [
"ArduinoJson",
"ArduinoJson@5.10.1",
"NeoPixelBus",
"AsyncMqttClient",
"ESPAsyncTCP",
"AsyncTCP",
"Adafruit PN532",
"Adafruit BusIO",
# test uninstalling
result = clirunner.invoke(
cmd_lib, ["-d", str(project_dir), "uninstall", "ArduinoJson"]
)
validate_cliresult(result)
config = ProjectConfig(os.path.join(str(project_dir), "platformio.ini"))
assert len(config.get("env:one", "lib_deps")) == 2
assert len(config.get("env:two", "lib_deps")) == 1
assert config.get("env:one", "lib_deps") == [
"mbed-sam-grove/LinkedList@%s" % ll_pkg_data["version"]["name"],
"https://github.com/OttoWinter/async-mqtt-client.git#v0.8.3 @ 0.8.3",
]
assert set(items1) == set(items2)
# test list
result = clirunner.invoke(cmd_lib, ["-d", str(project_dir), "list"])
validate_cliresult(result)
assert "Version: 0.8.3+sha." in result.stdout
assert (
"Source: git+https://github.com/OttoWinter/async-mqtt-client.git#v0.8.3"
in result.stdout
)
result = clirunner.invoke(
cmd_lib, ["-d", str(project_dir), "list", "--json-output"]
)
validate_cliresult(result)
data = {}
for key, value in json.loads(result.stdout).items():
data[os.path.basename(key)] = value
ame_lib = next(
item for item in data["one"] if item["name"] == "AsyncMqttClient-esphome"
)
ame_vcs = VCSClientFactory.new(ame_lib["__pkg_dir"], ame_lib["__src_url"])
assert data["two"] == []
assert "__pkg_dir" in data["one"][0]
assert (
ame_lib["__src_url"]
== "git+https://github.com/OttoWinter/async-mqtt-client.git#v0.8.3"
)
assert ame_lib["version"] == ("0.8.3+sha.%s" % ame_vcs.get_current_revision())
def test_global_install_archive(clirunner, validate_cliresult, isolated_pio_core):
def test_update(clirunner, validate_cliresult, isolated_pio_core, tmpdir_factory):
storage_dir = tmpdir_factory.mktemp("test-updates")
result = clirunner.invoke(
cmd_lib,
["-d", str(storage_dir), "install", "ArduinoJson @ 5.10.1", "Blynk @ ~0.5.0"],
)
validate_cliresult(result)
result = clirunner.invoke(
cmd_lib, ["-d", str(storage_dir), "update", "--dry-run", "--json-output"]
)
validate_cliresult(result)
outdated = json.loads(result.stdout)
assert len(outdated) == 2
# ArduinoJson
assert outdated[0]["version"] == "5.10.1"
assert outdated[0]["versionWanted"] is None
assert semantic_version.Version(
outdated[0]["versionLatest"]
) > semantic_version.Version("6.16.0")
# Blynk
assert outdated[1]["version"] == "0.5.4"
assert outdated[1]["versionWanted"] is None
assert semantic_version.Version(
outdated[1]["versionLatest"]
) > semantic_version.Version("0.6.0")
# check with spec
result = clirunner.invoke(
cmd_lib,
[
"-g",
"install",
"https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip",
"https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip@5.8.2",
"SomeLib=http://dl.platformio.org/libraries/archives/0/9540.tar.gz",
"https://github.com/Pedroalbuquerque/ESP32WebServer/archive/master.zip",
"-d",
str(storage_dir),
"update",
"--dry-run",
"--json-output",
"ArduinoJson @ ^5",
],
)
validate_cliresult(result)
# incorrect requirements
outdated = json.loads(result.stdout)
assert outdated[0]["version"] == "5.10.1"
assert outdated[0]["versionWanted"] == "5.13.4"
assert semantic_version.Version(
outdated[0]["versionLatest"]
) > semantic_version.Version("6.16.0")
# update with spec
result = clirunner.invoke(
cmd_lib,
[
"-g",
"install",
"https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip@1.2.3",
],
cmd_lib, ["-d", str(storage_dir), "update", "--silent", "ArduinoJson @ ^5.10.1"]
)
assert result.exit_code != 0
items1 = [d.basename for d in isolated_pio_core.join("lib").listdir()]
items2 = ["ArduinoJson", "SomeLib_ID54", "OneWire", "ESP32WebServer"]
assert set(items1) >= set(items2)
def test_global_install_repository(clirunner, validate_cliresult, isolated_pio_core):
validate_cliresult(result)
result = clirunner.invoke(
cmd_lib,
[
"-g",
"install",
"https://github.com/gioblu/PJON.git#3.0",
"https://github.com/gioblu/PJON.git#6.2",
"https://github.com/bblanchon/ArduinoJson.git",
"https://gitlab.com/ivankravets/rs485-nodeproto.git",
"https://github.com/platformio/platformio-libmirror.git",
# "https://developer.mbed.org/users/simon/code/TextLCD/",
"knolleary/pubsubclient#bef58148582f956dfa772687db80c44e2279a163",
],
cmd_lib, ["-d", str(storage_dir), "list", "--json-output"]
)
validate_cliresult(result)
items1 = [d.basename for d in isolated_pio_core.join("lib").listdir()]
items2 = [
"PJON",
"PJON@src-79de467ebe19de18287becff0a1fb42d",
"ArduinoJson@src-69ebddd821f771debe7ee734d3c7fa81",
"rs485-nodeproto",
"platformio-libmirror",
"PubSubClient",
]
assert set(items1) >= set(items2)
items = json.loads(result.stdout)
assert len(items) == 2
assert items[0]["version"] == "5.13.4"
assert items[1]["version"] == "0.5.4"
def test_install_duplicates(clirunner, validate_cliresult, without_internet):
# registry
# Check incompatible
result = clirunner.invoke(
cmd_lib,
["-g", "install", "http://dl.platformio.org/libraries/archives/0/9540.tar.gz"],
cmd_lib, ["-d", str(storage_dir), "update", "--dry-run", "ArduinoJson @ ^5"]
)
validate_cliresult(result)
assert "is already installed" in result.output
# archive
result = clirunner.invoke(
cmd_lib,
[
"-g",
"install",
"https://github.com/Pedroalbuquerque/ESP32WebServer/archive/master.zip",
],
)
validate_cliresult(result)
assert "is already installed" in result.output
# repository
result = clirunner.invoke(
cmd_lib,
["-g", "install", "https://github.com/platformio/platformio-libmirror.git"],
)
validate_cliresult(result)
assert "is already installed" in result.output
def test_global_lib_list(clirunner, validate_cliresult):
result = clirunner.invoke(cmd_lib, ["-g", "list"])
validate_cliresult(result)
assert all(
[
n in result.output
for n in (
"Source: https://github.com/Pedroalbuquerque/ESP32WebServer/archive/master.zip",
"Version: 5.10.1",
"Source: git+https://github.com/gioblu/PJON.git#3.0",
"Version: 1fb26fd",
)
]
)
result = clirunner.invoke(cmd_lib, ["-g", "list", "--json-output"])
assert all(
[
n in result.output
for n in (
"__pkg_dir",
'"__src_url": "git+https://gitlab.com/ivankravets/rs485-nodeproto.git"',
'"version": "5.10.1"',
)
]
)
items1 = [i["name"] for i in json.loads(result.output)]
items2 = [
"ESP32WebServer",
"ArduinoJson",
"ArduinoJson",
"ArduinoJson",
"ArduinoJson",
"AsyncMqttClient",
"AsyncTCP",
"SomeLib",
"ESPAsyncTCP",
"NeoPixelBus",
"OneWire",
"PJON",
"PJON",
"PubSubClient",
"Adafruit PN532",
"Adafruit BusIO",
"platformio-libmirror",
"rs485-nodeproto",
]
assert sorted(items1) == sorted(items2)
versions1 = [
"{name}@{version}".format(**item) for item in json.loads(result.output)
]
versions2 = [
"ArduinoJson@5.8.2",
"ArduinoJson@5.10.1",
"AsyncMqttClient@0.8.2",
"NeoPixelBus@2.2.4",
"PJON@07fe9aa",
"PJON@1fb26fd",
"PubSubClient@bef5814",
"Adafruit PN532@1.2.0",
]
assert set(versions1) >= set(versions2)
def test_global_lib_update_check(clirunner, validate_cliresult):
result = clirunner.invoke(
cmd_lib, ["-g", "update", "--only-check", "--json-output"]
)
validate_cliresult(result)
output = json.loads(result.output)
assert set(["ESPAsyncTCP", "NeoPixelBus"]) == set([l["name"] for l in output])
def test_global_lib_update(clirunner, validate_cliresult):
# update library using package directory
result = clirunner.invoke(
cmd_lib, ["-g", "update", "NeoPixelBus", "--only-check", "--json-output"]
)
validate_cliresult(result)
oudated = json.loads(result.output)
assert len(oudated) == 1
assert "__pkg_dir" in oudated[0]
result = clirunner.invoke(cmd_lib, ["-g", "update", oudated[0]["__pkg_dir"]])
validate_cliresult(result)
assert "Uninstalling NeoPixelBus @ 2.2.4" in result.output
# update rest libraries
result = clirunner.invoke(cmd_lib, ["-g", "update"])
validate_cliresult(result)
assert result.output.count("[Detached]") == 5
assert result.output.count("[Up-to-date]") == 12
# update unknown library
result = clirunner.invoke(cmd_lib, ["-g", "update", "Unknown"])
assert result.exit_code != 0
assert isinstance(result.exception, exception.UnknownPackage)
def test_global_lib_uninstall(clirunner, validate_cliresult, isolated_pio_core):
# uninstall using package directory
result = clirunner.invoke(cmd_lib, ["-g", "list", "--json-output"])
validate_cliresult(result)
items = json.loads(result.output)
items = sorted(items, key=lambda item: item["__pkg_dir"])
result = clirunner.invoke(cmd_lib, ["-g", "uninstall", items[0]["__pkg_dir"]])
validate_cliresult(result)
assert ("Uninstalling %s" % items[0]["name"]) in result.output
# uninstall the rest libraries
result = clirunner.invoke(
cmd_lib,
[
"-g",
"uninstall",
"OneWire",
"https://github.com/bblanchon/ArduinoJson.git",
"ArduinoJson@!=5.6.7",
"Adafruit PN532",
],
)
validate_cliresult(result)
items1 = [d.basename for d in isolated_pio_core.join("lib").listdir()]
items2 = [
"rs485-nodeproto",
"platformio-libmirror",
"PubSubClient",
"ArduinoJson@src-69ebddd821f771debe7ee734d3c7fa81",
"ESPAsyncTCP",
"ESP32WebServer",
"NeoPixelBus",
"PJON",
"AsyncMqttClient",
"ArduinoJson",
"SomeLib_ID54",
"PJON@src-79de467ebe19de18287becff0a1fb42d",
"AsyncTCP",
]
assert set(items1) == set(items2)
# uninstall unknown library
result = clirunner.invoke(cmd_lib, ["-g", "uninstall", "Unknown"])
assert result.exit_code != 0
assert isinstance(result.exception, exception.UnknownPackage)
def test_lib_show(clirunner, validate_cliresult):
result = clirunner.invoke(cmd_lib, ["show", "64"])
validate_cliresult(result)
assert all([s in result.output for s in ("ArduinoJson", "Arduino", "Atmel AVR")])
result = clirunner.invoke(cmd_lib, ["show", "OneWire", "--json-output"])
validate_cliresult(result)
assert "OneWire" in result.output
def test_lib_builtin(clirunner, validate_cliresult):
result = clirunner.invoke(cmd_lib, ["builtin"])
validate_cliresult(result)
result = clirunner.invoke(cmd_lib, ["builtin", "--json-output"])
validate_cliresult(result)
def test_lib_stats(clirunner, validate_cliresult):
result = clirunner.invoke(cmd_lib, ["stats"])
validate_cliresult(result)
assert all(
[
s in result.output
for s in ("UPDATED", "POPULAR", "https://platformio.org/lib/show")
]
)
result = clirunner.invoke(cmd_lib, ["stats", "--json-output"])
validate_cliresult(result)
assert set(
[
"dlweek",
"added",
"updated",
"topkeywords",
"dlmonth",
"dlday",
"lastkeywords",
]
) == set(json.loads(result.output).keys())
assert "Incompatible" in result.stdout

View File

@ -0,0 +1,348 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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.
import json
import re
from platformio import exception
from platformio.commands import PlatformioCLI
from platformio.commands.lib.command import cli as cmd_lib
from platformio.package.exception import UnknownPackageError
PlatformioCLI.leftover_args = ["--json-output"] # hook for click
def test_search(clirunner, validate_cliresult):
result = clirunner.invoke(cmd_lib, ["search", "DHT22"])
validate_cliresult(result)
match = re.search(r"Found\s+(\d+)\slibraries:", result.output)
assert int(match.group(1)) > 2
result = clirunner.invoke(cmd_lib, ["search", "DHT22", "--platform=timsp430"])
validate_cliresult(result)
match = re.search(r"Found\s+(\d+)\slibraries:", result.output)
assert int(match.group(1)) > 1
def test_global_install_registry(clirunner, validate_cliresult, isolated_pio_core):
result = clirunner.invoke(
cmd_lib,
[
"-g",
"install",
"64",
"ArduinoJson@~5.10.0",
"547@2.2.4",
"AsyncMqttClient@<=0.8.2",
"Adafruit PN532@1.2.0",
],
)
validate_cliresult(result)
# install unknown library
result = clirunner.invoke(cmd_lib, ["-g", "install", "Unknown"])
assert result.exit_code != 0
assert isinstance(result.exception, UnknownPackageError)
items1 = [d.basename for d in isolated_pio_core.join("lib").listdir()]
items2 = [
"ArduinoJson",
"ArduinoJson@5.10.1",
"NeoPixelBus",
"AsyncMqttClient",
"ESPAsyncTCP",
"AsyncTCP",
"Adafruit PN532",
"Adafruit BusIO",
]
assert set(items1) == set(items2)
def test_global_install_archive(clirunner, validate_cliresult, isolated_pio_core):
result = clirunner.invoke(
cmd_lib,
[
"-g",
"install",
"https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip",
"https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip@5.8.2",
"SomeLib=https://dl.registry.platformio.org/download/milesburton/library/DallasTemperature/3.8.1/DallasTemperature-3.8.1.tar.gz",
"https://github.com/Pedroalbuquerque/ESP32WebServer/archive/master.zip",
],
)
validate_cliresult(result)
# incorrect requirements
result = clirunner.invoke(
cmd_lib,
[
"-g",
"install",
"https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip@1.2.3",
],
)
assert result.exit_code != 0
items1 = [d.basename for d in isolated_pio_core.join("lib").listdir()]
items2 = [
"ArduinoJson",
"SomeLib",
"OneWire",
"ESP32WebServer@src-a1a3c75631882b35702e71966ea694e8",
]
assert set(items1) >= set(items2)
def test_global_install_repository(clirunner, validate_cliresult, isolated_pio_core):
result = clirunner.invoke(
cmd_lib,
[
"-g",
"install",
"https://github.com/gioblu/PJON.git#3.0",
"https://github.com/gioblu/PJON.git#6.2",
"https://github.com/bblanchon/ArduinoJson.git",
"https://github.com/platformio/platformio-libmirror.git",
# "https://developer.mbed.org/users/simon/code/TextLCD/",
"https://github.com/knolleary/pubsubclient#bef58148582f956dfa772687db80c44e2279a163",
],
)
validate_cliresult(result)
items1 = [d.basename for d in isolated_pio_core.join("lib").listdir()]
items2 = [
"PJON@src-1204e8bbd80de05e54e171b3a07bcc3f",
"PJON@src-79de467ebe19de18287becff0a1fb42d",
"ArduinoJson@src-69ebddd821f771debe7ee734d3c7fa81",
"platformio-libmirror@src-b7e674cad84244c61b436fcea8f78377",
"PubSubClient@src-98ec699a461a31615982e5adaaefadda",
]
assert set(items1) >= set(items2)
def test_install_duplicates(clirunner, validate_cliresult, without_internet):
# registry
result = clirunner.invoke(
cmd_lib,
[
"-g",
"install",
"https://dl.registry.platformio.org/download/milesburton/library/DallasTemperature/3.8.1/DallasTemperature-3.8.1.tar.gz",
],
)
validate_cliresult(result)
assert "is already installed" in result.output
# archive
result = clirunner.invoke(
cmd_lib,
[
"-g",
"install",
"https://github.com/Pedroalbuquerque/ESP32WebServer/archive/master.zip",
],
)
validate_cliresult(result)
assert "is already installed" in result.output
# repository
result = clirunner.invoke(
cmd_lib,
["-g", "install", "https://github.com/platformio/platformio-libmirror.git"],
)
validate_cliresult(result)
assert "is already installed" in result.output
def test_global_lib_list(clirunner, validate_cliresult):
result = clirunner.invoke(cmd_lib, ["-g", "list"])
validate_cliresult(result)
assert all(
[
n in result.output
for n in (
"Source: https://github.com/Pedroalbuquerque/ESP32WebServer/archive/master.zip",
"Version: 5.10.1",
"Source: git+https://github.com/gioblu/PJON.git#3.0",
"Version: 3.0.0+sha.1fb26fd",
)
]
)
result = clirunner.invoke(cmd_lib, ["-g", "list", "--json-output"])
assert all(
[
n in result.output
for n in (
"__pkg_dir",
'"__src_url": "git+https://github.com/gioblu/PJON.git#6.2"',
'"version": "5.10.1"',
)
]
)
items1 = [i["name"] for i in json.loads(result.output)]
items2 = [
"Adafruit BusIO",
"Adafruit PN532",
"ArduinoJson",
"ArduinoJson",
"ArduinoJson",
"ArduinoJson",
"AsyncMqttClient",
"AsyncTCP",
"DallasTemperature",
"ESP32WebServer",
"ESPAsyncTCP",
"NeoPixelBus",
"OneWire",
"PJON",
"PJON",
"platformio-libmirror",
"PubSubClient",
]
assert sorted(items1) == sorted(items2)
versions1 = [
"{name}@{version}".format(**item) for item in json.loads(result.output)
]
versions2 = [
"ArduinoJson@5.8.2",
"ArduinoJson@5.10.1",
"AsyncMqttClient@0.8.2",
"NeoPixelBus@2.2.4",
"PJON@6.2.0+sha.07fe9aa",
"PJON@3.0.0+sha.1fb26fd",
"PubSubClient@2.6.0+sha.bef5814",
"Adafruit PN532@1.2.0",
]
assert set(versions1) >= set(versions2)
def test_global_lib_update_check(clirunner, validate_cliresult):
result = clirunner.invoke(cmd_lib, ["-g", "update", "--dry-run", "--json-output"])
validate_cliresult(result)
output = json.loads(result.output)
assert set(["ESPAsyncTCP", "NeoPixelBus"]) == set([lib["name"] for lib in output])
def test_global_lib_update(clirunner, validate_cliresult):
# update library using package directory
result = clirunner.invoke(
cmd_lib, ["-g", "update", "NeoPixelBus", "--dry-run", "--json-output"]
)
validate_cliresult(result)
oudated = json.loads(result.output)
assert len(oudated) == 1
assert "__pkg_dir" in oudated[0]
result = clirunner.invoke(cmd_lib, ["-g", "update", oudated[0]["__pkg_dir"]])
validate_cliresult(result)
assert "Removing NeoPixelBus @ 2.2.4" in result.output
# update rest libraries
result = clirunner.invoke(cmd_lib, ["-g", "update"])
validate_cliresult(result)
assert result.output.count("[Detached]") == 1
assert result.output.count("[Up-to-date]") == 15
# update unknown library
result = clirunner.invoke(cmd_lib, ["-g", "update", "Unknown"])
assert result.exit_code != 0
assert isinstance(result.exception, UnknownPackageError)
def test_global_lib_uninstall(clirunner, validate_cliresult, isolated_pio_core):
# uninstall using package directory
result = clirunner.invoke(cmd_lib, ["-g", "list", "--json-output"])
validate_cliresult(result)
items = json.loads(result.output)
items = sorted(items, key=lambda item: item["__pkg_dir"])
result = clirunner.invoke(cmd_lib, ["-g", "uninstall", items[0]["__pkg_dir"]])
validate_cliresult(result)
assert ("Removing %s" % items[0]["name"]) in result.output
# uninstall the rest libraries
result = clirunner.invoke(
cmd_lib,
[
"-g",
"uninstall",
"OneWire",
"https://github.com/bblanchon/ArduinoJson.git",
"ArduinoJson@!=5.6.7",
"Adafruit PN532",
],
)
validate_cliresult(result)
items1 = [d.basename for d in isolated_pio_core.join("lib").listdir()]
items2 = [
"ArduinoJson",
"ArduinoJson@src-69ebddd821f771debe7ee734d3c7fa81",
"AsyncMqttClient",
"AsyncTCP",
"ESP32WebServer@src-a1a3c75631882b35702e71966ea694e8",
"ESPAsyncTCP",
"NeoPixelBus",
"PJON@src-1204e8bbd80de05e54e171b3a07bcc3f",
"PJON@src-79de467ebe19de18287becff0a1fb42d",
"platformio-libmirror@src-b7e674cad84244c61b436fcea8f78377",
"PubSubClient@src-98ec699a461a31615982e5adaaefadda",
"SomeLib",
]
assert set(items1) == set(items2)
# uninstall unknown library
result = clirunner.invoke(cmd_lib, ["-g", "uninstall", "Unknown"])
assert result.exit_code != 0
assert isinstance(result.exception, UnknownPackageError)
def test_lib_show(clirunner, validate_cliresult):
result = clirunner.invoke(cmd_lib, ["show", "64"])
validate_cliresult(result)
assert all([s in result.output for s in ("ArduinoJson", "Arduino", "Atmel AVR")])
result = clirunner.invoke(cmd_lib, ["show", "OneWire", "--json-output"])
validate_cliresult(result)
assert "OneWire" in result.output
def test_lib_builtin(clirunner, validate_cliresult):
result = clirunner.invoke(cmd_lib, ["builtin"])
validate_cliresult(result)
result = clirunner.invoke(cmd_lib, ["builtin", "--json-output"])
validate_cliresult(result)
def test_lib_stats(clirunner, validate_cliresult):
result = clirunner.invoke(cmd_lib, ["stats"])
validate_cliresult(result)
assert all(
[
s in result.output
for s in ("UPDATED", "POPULAR", "https://platformio.org/lib/show")
]
)
result = clirunner.invoke(cmd_lib, ["stats", "--json-output"])
validate_cliresult(result)
assert set(
[
"dlweek",
"added",
"updated",
"topkeywords",
"dlmonth",
"dlday",
"lastkeywords",
]
) == set(json.loads(result.output).keys())

View File

@ -16,6 +16,7 @@ import os
import time
import pytest
import semantic_version
from platformio import fs, util
from platformio.package.exception import (
@ -201,6 +202,12 @@ def test_install_from_registry(isolated_pio_core, tmpdir_factory):
assert lm.get_package("OneWire").metadata.version.major >= 2
assert len(lm.get_installed()) == 6
# test conflicted names
lm = LibraryPackageManager(str(tmpdir_factory.mktemp("conflicted-storage")))
lm.install("4@2.6.1", silent=True)
lm.install("5357@2.6.1", silent=True)
assert len(lm.get_installed()) == 2
# Tools
tm = ToolPackageManager(str(tmpdir_factory.mktemp("tool-storage")))
pkg = tm.install("platformio/tool-stlink @ ~1.10400.0", silent=True)
@ -340,3 +347,81 @@ def test_uninstall(isolated_pio_core, tmpdir_factory):
assert lm.install("AsyncMqttClient-esphome @ 0.8.4", silent=True)
assert lm.uninstall("AsyncMqttClient-esphome", silent=True)
assert len(lm.get_installed()) == 0
def test_registry(isolated_pio_core):
lm = LibraryPackageManager()
# reveal ID
assert lm.reveal_registry_package_id(PackageSpec(id=13)) == 13
assert lm.reveal_registry_package_id(PackageSpec(name="OneWire"), silent=True) == 1
with pytest.raises(UnknownPackageError):
lm.reveal_registry_package_id(PackageSpec(name="/non-existing-package/"))
# fetch package data
assert lm.fetch_registry_package(PackageSpec(id=1))["name"] == "OneWire"
assert lm.fetch_registry_package(PackageSpec(name="ArduinoJson"))["id"] == 64
assert (
lm.fetch_registry_package(
PackageSpec(id=13, owner="adafruit", name="Renamed library")
)["name"]
== "Adafruit GFX Library"
)
with pytest.raises(UnknownPackageError):
lm.fetch_registry_package(
PackageSpec(owner="unknown<>owner", name="/non-existing-package/")
)
with pytest.raises(UnknownPackageError):
lm.fetch_registry_package(PackageSpec(name="/non-existing-package/"))
def test_update_with_metadata(isolated_pio_core, tmpdir_factory):
storage_dir = tmpdir_factory.mktemp("storage")
lm = LibraryPackageManager(str(storage_dir))
pkg = lm.install("ArduinoJson @ 5.10.1", silent=True)
# tesy latest
outdated = lm.outdated(pkg)
assert str(outdated.current) == "5.10.1"
assert outdated.wanted is None
assert outdated.latest > outdated.current
assert outdated.latest > semantic_version.Version("5.99.99")
# test wanted
outdated = lm.outdated(pkg, PackageSpec("ArduinoJson@~5"))
assert str(outdated.current) == "5.10.1"
assert str(outdated.wanted) == "5.13.4"
assert outdated.latest > semantic_version.Version("6.16.0")
# update to the wanted 5.x
new_pkg = lm.update("ArduinoJson@^5", PackageSpec("ArduinoJson@^5"), silent=True)
assert str(new_pkg.metadata.version) == "5.13.4"
# check that old version is removed
assert len(lm.get_installed()) == 1
# update to the latest
lm = LibraryPackageManager(str(storage_dir))
pkg = lm.update("ArduinoJson", silent=True)
assert pkg.metadata.version == outdated.latest
def test_update_without_metadata(isolated_pio_core, tmpdir_factory):
storage_dir = tmpdir_factory.mktemp("storage")
storage_dir.join("legacy-package").mkdir().join("library.json").write(
'{"name": "AsyncMqttClient-esphome", "version": "0.8.2"}'
)
storage_dir.join("legacy-dep").mkdir().join("library.json").write(
'{"name": "AsyncTCP-esphome", "version": "1.1.1"}'
)
lm = LibraryPackageManager(str(storage_dir))
pkg = lm.get_package("AsyncMqttClient-esphome")
outdated = lm.outdated(pkg)
assert len(lm.get_installed()) == 2
assert str(pkg.metadata.version) == "0.8.2"
assert outdated.latest > semantic_version.Version("0.8.2")
# update
lm = LibraryPackageManager(str(storage_dir))
new_pkg = lm.update(pkg, silent=True)
assert len(lm.get_installed()) == 3
assert new_pkg.metadata.spec.owner == "ottowinter"

View File

@ -17,7 +17,27 @@ import os
import jsondiff
import semantic_version
from platformio.package.meta import PackageMetaData, PackageSpec, PackageType
from platformio.package.meta import (
PackageMetaData,
PackageOutdatedResult,
PackageSpec,
PackageType,
)
def test_outdated_result():
result = PackageOutdatedResult(current="1.2.3", latest="2.0.0")
assert result.is_outdated()
assert result.is_outdated(allow_incompatible=True)
result = PackageOutdatedResult(current="1.2.3", latest="2.0.0", wanted="1.5.4")
assert result.is_outdated()
assert result.is_outdated(allow_incompatible=True)
result = PackageOutdatedResult(current="1.2.3", latest="2.0.0", wanted="1.2.3")
assert not result.is_outdated()
assert result.is_outdated(allow_incompatible=True)
result = PackageOutdatedResult(current="1.2.3", latest="2.0.0", detached=True)
assert not result.is_outdated()
assert not result.is_outdated(allow_incompatible=True)
def test_spec_owner():
@ -45,9 +65,16 @@ def test_spec_name():
def test_spec_requirements():
assert PackageSpec("foo@1.2.3") == PackageSpec(name="foo", requirements="1.2.3")
assert PackageSpec(
name="foo", requirements=semantic_version.Version("1.2.3")
) == PackageSpec(name="foo", requirements="1.2.3")
assert PackageSpec("bar @ ^1.2.3") == PackageSpec(name="bar", requirements="^1.2.3")
assert PackageSpec("13 @ ~2.0") == PackageSpec(id=13, requirements="~2.0")
assert PackageSpec(
name="hello", requirements=semantic_version.SimpleSpec("~1.2.3")
) == PackageSpec(name="hello", requirements="~1.2.3")
spec = PackageSpec("id=20 @ !=1.2.3,<2.0")
assert not spec.external
assert isinstance(spec.requirements, semantic_version.SimpleSpec)
assert semantic_version.Version("1.3.0-beta.1") in spec.requirements
assert spec == PackageSpec(id=20, requirements="!=1.2.3,<2.0")
@ -88,7 +115,8 @@ def test_spec_external_urls():
"Custom-Name="
"https://github.com/platformio/platformio-core/archive/develop.tar.gz@4.4.0"
)
assert spec.is_custom_name()
assert spec.external
assert spec.has_custom_name()
assert spec.name == "Custom-Name"
assert spec == PackageSpec(
url="https://github.com/platformio/platformio-core/archive/develop.tar.gz",
@ -163,6 +191,24 @@ def test_spec_as_dict():
)
def test_spec_as_dependency():
assert PackageSpec("owner/pkgname").as_dependency() == "owner/pkgname"
assert PackageSpec(owner="owner", name="pkgname").as_dependency() == "owner/pkgname"
assert PackageSpec("bob/foo @ ^1.2.3").as_dependency() == "bob/foo@^1.2.3"
assert (
PackageSpec(
"https://github.com/o/r/a/develop.zip?param=value @ !=2"
).as_dependency()
== "https://github.com/o/r/a/develop.zip?param=value @ !=2"
)
assert (
PackageSpec(
"wolfSSL=https://os.mbed.com/users/wolfSSL/code/wolfSSL/"
).as_dependency()
== "wolfSSL=https://os.mbed.com/users/wolfSSL/code/wolfSSL/"
)
def test_metadata_as_dict():
metadata = PackageMetaData(PackageType.LIBRARY, "foo", "1.2.3")
# test setter

View File

@ -88,7 +88,9 @@ def test_check_and_update_libraries(clirunner, isolated_pio_core, validate_clire
validate_cliresult(result)
assert "There are the new updates for libraries (ArduinoJson)" in result.output
assert "Please wait while updating libraries" in result.output
assert re.search(r"Updating ArduinoJson\s+@ 6.12.0\s+\[[\d\.]+\]", result.output)
assert re.search(
r"Updating bblanchon/ArduinoJson\s+6\.12\.0\s+\[[\d\.]+\]", result.output
)
# check updated version
result = clirunner.invoke(cli_pio, ["lib", "-g", "list", "--json-output"])