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

View File

@@ -30,7 +30,9 @@ class HTTPClientError(PlatformioException):
class HTTPClient(object): class HTTPClient(object):
def __init__(self, base_url): def __init__(
self, base_url,
):
if base_url.endswith("/"): if base_url.endswith("/"):
base_url = base_url[:-1] base_url = base_url[:-1]
self.base_url = base_url self.base_url = base_url
@@ -51,6 +53,7 @@ class HTTPClient(object):
self._session.close() self._session.close()
self._session = None self._session = None
@util.throttle(500)
def send_request(self, method, path, **kwargs): def send_request(self, method, path, **kwargs):
# check Internet before and resolve issue with 60 seconds timeout # check Internet before and resolve issue with 60 seconds timeout
# print(self, method, path, kwargs) # 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 time
import click import click
import semantic_version
from tabulate import tabulate from tabulate import tabulate
from platformio import exception, fs, util from platformio import exception, fs, util
from platformio.commands import PlatformioCLI 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.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.proc import is_ci
from platformio.project.config import ProjectConfig from platformio.project.config import ProjectConfig
from platformio.project.exception import InvalidProjectConfError
from platformio.project.helpers import get_project_dir, is_platformio_project from platformio.project.helpers import get_project_dir, is_platformio_project
try: try:
@@ -124,89 +129,106 @@ def cli(ctx, **options):
@cli.command("install", short_help="Install library") @cli.command("install", short_help="Install library")
@click.argument("libraries", required=False, nargs=-1, metavar="[LIBRARY...]") @click.argument("libraries", required=False, nargs=-1, metavar="[LIBRARY...]")
@click.option( @click.option(
"--save", "--save/--no-save",
is_flag=True, 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("-s", "--silent", is_flag=True, help="Suppress progress reporting")
@click.option( @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( @click.option(
"-f", "--force", is_flag=True, help="Reinstall/redownload library if exists" "-f", "--force", is_flag=True, help="Reinstall/redownload library if exists"
) )
@click.pass_context @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 ctx, libraries, save, silent, interactive, force
): ):
storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY] storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY]
storage_libdeps = ctx.meta.get(CTX_META_STORAGE_LIBDEPS_KEY, []) storage_libdeps = ctx.meta.get(CTX_META_STORAGE_LIBDEPS_KEY, [])
installed_manifests = {} installed_pkgs = {}
for storage_dir in storage_dirs: for storage_dir in storage_dirs:
if not silent and (libraries or storage_dir in storage_libdeps): if not silent and (libraries or storage_dir in storage_libdeps):
print_storage_header(storage_dirs, storage_dir) print_storage_header(storage_dirs, storage_dir)
lm = LibraryManager(storage_dir) lm = LibraryPackageManager(storage_dir)
if libraries: if libraries:
for library in libraries: installed_pkgs = {
pkg_dir = lm.install( library: lm.install(library, silent=silent, force=force)
library, silent=silent, interactive=interactive, force=force for library in libraries
) }
installed_manifests[library] = lm.load_manifest(pkg_dir)
elif storage_dir in storage_libdeps: elif storage_dir in storage_libdeps:
builtin_lib_storages = None builtin_lib_storages = None
for library in storage_libdeps[storage_dir]: for library in storage_libdeps[storage_dir]:
try: try:
pkg_dir = lm.install( lm.install(library, silent=silent, force=force)
library, silent=silent, interactive=interactive, force=force except UnknownPackageError as e:
)
installed_manifests[library] = lm.load_manifest(pkg_dir)
except exception.LibNotFound as e:
if builtin_lib_storages is None: if builtin_lib_storages is None:
builtin_lib_storages = get_builtin_libs() builtin_lib_storages = get_builtin_libs()
if not silent or not is_builtin_lib(builtin_lib_storages, library): if not silent or not is_builtin_lib(builtin_lib_storages, library):
click.secho("Warning! %s" % e, fg="yellow") click.secho("Warning! %s" % e, fg="yellow")
if not save or not libraries: if save and installed_pkgs:
return _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, []) input_dirs = ctx.meta.get(CTX_META_INPUT_DIRS_KEY, [])
project_environments = ctx.meta[CTX_META_PROJECT_ENVIRONMENTS_KEY] project_environments = ctx.meta[CTX_META_PROJECT_ENVIRONMENTS_KEY]
for input_dir in input_dirs: for input_dir in input_dirs:
config = ProjectConfig.get_instance(os.path.join(input_dir, "platformio.ini")) if not is_platformio_project(input_dir):
config.validate(project_environments)
for env in config.envs():
if project_environments and env not in project_environments:
continue continue
config.expand_interpolations = False save_project_libdeps(input_dir, specs, project_environments, action=action)
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()
@cli.command("uninstall", short_help="Uninstall libraries") @cli.command("uninstall", short_help="Remove libraries")
@click.argument("libraries", nargs=-1, metavar="[LIBRARY...]") @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 @click.pass_context
def lib_uninstall(ctx, libraries): def lib_uninstall(ctx, libraries, save, silent):
storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY] storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY]
uninstalled_pkgs = {}
for storage_dir in storage_dirs: for storage_dir in storage_dirs:
print_storage_header(storage_dirs, storage_dir) print_storage_header(storage_dirs, storage_dir)
lm = LibraryManager(storage_dir) lm = LibraryPackageManager(storage_dir)
for library in libraries: uninstalled_pkgs = {
lm.uninstall(library) 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") @cli.command("update", short_help="Update installed libraries")
@@ -220,42 +242,53 @@ def lib_uninstall(ctx, libraries):
@click.option( @click.option(
"--dry-run", is_flag=True, help="Do not update, only check for the new versions" "--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.option("--json-output", is_flag=True)
@click.pass_context @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] storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY]
only_check = dry_run or only_check only_check = dry_run or only_check
json_result = {} json_result = {}
for storage_dir in storage_dirs: for storage_dir in storage_dirs:
if not json_output: if not json_output:
print_storage_header(storage_dirs, storage_dir) print_storage_header(storage_dirs, storage_dir)
lm = LibraryManager(storage_dir) lm = LibraryPackageManager(storage_dir)
_libraries = libraries or lm.get_installed()
_libraries = libraries
if not _libraries:
_libraries = [manifest["__pkg_dir"] for manifest in lm.get_installed()]
if only_check and json_output: if only_check and json_output:
result = [] result = []
for library in _libraries: for library in _libraries:
pkg_dir = library if os.path.isdir(library) else None spec = None
requirements = None pkg = None
url = None if isinstance(library, PackageSourceItem):
if not pkg_dir: pkg = library
name, requirements, url = lm.parse_pkg_uri(library) else:
pkg_dir = lm.get_package_dir(name, requirements, url) spec = PackageSpec(library)
if not pkg_dir: pkg = lm.get_package(spec)
if not pkg:
continue continue
latest = lm.outdated(pkg_dir, requirements) outdated = lm.outdated(pkg, spec)
if not latest: if not outdated.is_outdated(allow_incompatible=True):
continue continue
manifest = lm.load_manifest(pkg_dir) manifest = lm.legacy_load_manifest(pkg)
manifest["versionLatest"] = latest manifest["versionWanted"] = (
str(outdated.wanted) if outdated.wanted else None
)
manifest["versionLatest"] = (
str(outdated.latest) if outdated.latest else None
)
result.append(manifest) result.append(manifest)
json_result[storage_dir] = result json_result[storage_dir] = result
else: else:
for library in _libraries: 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: if json_output:
return click.echo( return click.echo(
@@ -276,8 +309,8 @@ def lib_list(ctx, json_output):
for storage_dir in storage_dirs: for storage_dir in storage_dirs:
if not json_output: if not json_output:
print_storage_header(storage_dirs, storage_dir) print_storage_header(storage_dirs, storage_dir)
lm = LibraryManager(storage_dir) lm = LibraryPackageManager(storage_dir)
items = lm.get_installed() items = lm.legacy_get_installed()
if json_output: if json_output:
json_result[storage_dir] = items json_result[storage_dir] = items
elif items: elif items:
@@ -301,6 +334,7 @@ def lib_list(ctx, json_output):
@click.option("--json-output", is_flag=True) @click.option("--json-output", is_flag=True)
@click.option("--page", type=click.INT, default=1) @click.option("--page", type=click.INT, default=1)
@click.option("--id", multiple=True) @click.option("--id", multiple=True)
@click.option("-o", "--owner", multiple=True)
@click.option("-n", "--name", multiple=True) @click.option("-n", "--name", multiple=True)
@click.option("-a", "--author", multiple=True) @click.option("-a", "--author", multiple=True)
@click.option("-k", "--keyword", multiple=True) @click.option("-k", "--keyword", multiple=True)
@@ -404,12 +438,8 @@ def lib_builtin(storage, json_output):
@click.argument("library", metavar="[LIBRARY]") @click.argument("library", metavar="[LIBRARY]")
@click.option("--json-output", is_flag=True) @click.option("--json-output", is_flag=True)
def lib_show(library, json_output): def lib_show(library, json_output):
lm = LibraryManager() lib_id = LibraryPackageManager().reveal_registry_package_id(
name, requirements, _ = lm.parse_pkg_uri(library) library, silent=json_output
lib_id = lm.search_lib_id(
{"name": name, "requirements": requirements},
silent=json_output,
interactive=not json_output,
) )
lib = util.get_api_result("/lib/info/%d" % lib_id, cache_valid="1d") lib = util.get_api_result("/lib/info/%d" % lib_id, cache_valid="1d")
if json_output: 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, install_completion_code,
uninstall_completion_code, uninstall_completion_code,
) )
from platformio.managers.lib import LibraryManager
from platformio.managers.package import PackageManager from platformio.managers.package import PackageManager
from platformio.managers.platform import PlatformManager from platformio.managers.platform import PlatformManager
from platformio.package.manager.library import LibraryPackageManager
from platformio.project.config import ProjectConfig from platformio.project.config import ProjectConfig
@@ -73,7 +73,7 @@ def system_info(json_output):
} }
data["global_lib_nums"] = { data["global_lib_nums"] = {
"title": "Global Libraries", "title": "Global Libraries",
"value": len(LibraryManager().get_installed()), "value": len(LibraryPackageManager().get_installed()),
} }
data["dev_platform_nums"] = { data["dev_platform_nums"] = {
"title": "Development Platforms", "title": "Development Platforms",

View File

@@ -15,11 +15,11 @@
import click import click
from platformio import app from platformio import app
from platformio.commands.lib import CTX_META_STORAGE_DIRS_KEY from platformio.commands.lib.command import CTX_META_STORAGE_DIRS_KEY
from platformio.commands.lib import lib_update as cmd_lib_update 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.platform import platform_update as cmd_platform_update
from platformio.managers.core import update_core_packages from platformio.managers.core import update_core_packages
from platformio.managers.lib import LibraryManager from platformio.package.manager.library import LibraryPackageManager
@click.command( @click.command(
@@ -55,5 +55,5 @@ def cli(ctx, core_packages, only_check, dry_run):
click.echo() click.echo()
click.echo("Library Manager") click.echo("Library Manager")
click.echo("===============") 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) 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: if PY2:
import imp 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): class NotGlobalLibDir(UserSideException):
MESSAGE = ( MESSAGE = (

View File

@@ -21,13 +21,13 @@ import semantic_version
from platformio import __version__, app, exception, fs, telemetry, util from platformio import __version__, app, exception, fs, telemetry, util
from platformio.commands import PlatformioCLI from platformio.commands import PlatformioCLI
from platformio.commands.lib import CTX_META_STORAGE_DIRS_KEY from platformio.commands.lib.command import CTX_META_STORAGE_DIRS_KEY
from platformio.commands.lib import lib_update as cmd_lib_update 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.platform import platform_update as cmd_platform_update
from platformio.commands.upgrade import get_latest_version from platformio.commands.upgrade import get_latest_version
from platformio.managers.core import update_core_packages from platformio.managers.core import update_core_packages
from platformio.managers.lib import LibraryManager
from platformio.managers.platform import PlatformFactory, PlatformManager from platformio.managers.platform import PlatformFactory, PlatformManager
from platformio.package.manager.library import LibraryPackageManager
from platformio.proc import is_container from platformio.proc import is_container
@@ -240,7 +240,7 @@ def check_platformio_upgrade():
click.echo("") 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", {}) last_check = app.get_state_item("last_check", {})
interval = int(app.get_setting("check_%s_interval" % what)) * 3600 * 24 interval = int(app.get_setting("check_%s_interval" % what)) * 3600 * 24
if (time() - interval) < last_check.get(what + "_update", 0): if (time() - interval) < last_check.get(what + "_update", 0):
@@ -251,8 +251,9 @@ def check_internal_updates(ctx, what):
util.internet_on(raise_exception=True) util.internet_on(raise_exception=True)
pm = PlatformManager() if what == "platforms" else LibraryManager()
outdated_items = [] outdated_items = []
pm = PlatformManager() if what == "platforms" else LibraryPackageManager()
if isinstance(pm, PlatformManager):
for manifest in pm.get_installed(): for manifest in pm.get_installed():
if manifest["name"] in outdated_items: if manifest["name"] in outdated_items:
continue continue
@@ -265,6 +266,12 @@ def check_internal_updates(ctx, what):
] ]
if any(conds): if any(conds):
outdated_items.append(manifest["name"]) 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: if not outdated_items:
return 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) self.unpack(dlpath, tmp_dir)
os.remove(dlpath) os.remove(dlpath)
else: else:
vcs = VCSClientFactory.newClient(tmp_dir, url) vcs = VCSClientFactory.new(tmp_dir, url)
assert vcs.export() assert vcs.export()
src_manifest_dir = vcs.storage_dir src_manifest_dir = vcs.storage_dir
src_manifest["version"] = vcs.get_current_revision() src_manifest["version"] = vcs.get_current_revision()
@@ -628,9 +628,7 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
if "__src_url" in manifest: if "__src_url" in manifest:
try: try:
vcs = VCSClientFactory.newClient( vcs = VCSClientFactory.new(pkg_dir, manifest["__src_url"], silent=True)
pkg_dir, manifest["__src_url"], silent=True
)
except (AttributeError, exception.PlatformioException): except (AttributeError, exception.PlatformioException):
return None return None
if not vcs.can_be_updated: if not vcs.can_be_updated:
@@ -800,7 +798,7 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
return True return True
if "__src_url" in manifest: if "__src_url" in manifest:
vcs = VCSClientFactory.newClient(pkg_dir, manifest["__src_url"]) vcs = VCSClientFactory.new(pkg_dir, manifest["__src_url"])
assert vcs.update() assert vcs.update()
self._update_src_manifest( self._update_src_manifest(
dict(version=vcs.get_current_revision()), vcs.storage_dir dict(version=vcs.get_current_revision()), vcs.storage_dir

View File

@@ -20,7 +20,7 @@ import tempfile
import click import click
from platformio import app, compat, fs, util 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.meta import PackageSourceItem, PackageSpec
from platformio.package.unpack import FileUnpacker from platformio.package.unpack import FileUnpacker
from platformio.package.vcsclient import VCSClientFactory from platformio.package.vcsclient import VCSClientFactory
@@ -83,7 +83,7 @@ class PackageManagerInstallMixin(object):
msg = "Installing %s" % click.style(spec.humanize(), fg="cyan") msg = "Installing %s" % click.style(spec.humanize(), fg="cyan")
self.print_message(msg) self.print_message(msg)
if spec.url: if spec.external:
pkg = self.install_from_url(spec.url, spec, silent=silent) pkg = self.install_from_url(spec.url, spec, silent=silent)
else: else:
pkg = self.install_from_registry(spec, search_filters, silent=silent) pkg = self.install_from_registry(spec, search_filters, silent=silent)
@@ -152,7 +152,7 @@ class PackageManagerInstallMixin(object):
assert os.path.isfile(dl_path) assert os.path.isfile(dl_path)
self.unpack(dl_path, tmp_dir) self.unpack(dl_path, tmp_dir)
else: else:
vcs = VCSClientFactory.newClient(tmp_dir, url) vcs = VCSClientFactory.new(tmp_dir, url)
assert vcs.export() assert vcs.export()
root_dir = self.find_pkg_root(tmp_dir, spec) root_dir = self.find_pkg_root(tmp_dir, spec)
@@ -189,12 +189,20 @@ class PackageManagerInstallMixin(object):
# what to do with existing package? # what to do with existing package?
action = "overwrite" 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: if dst_pkg.metadata.spec.url != tmp_pkg.metadata.spec.url:
action = "detach-existing" action = "detach-existing"
elif tmp_pkg.metadata.spec.url: elif tmp_pkg.metadata.spec.external:
action = "detach-new" 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 = ( action = (
"detach-existing" "detach-existing"
if tmp_pkg.metadata.version > dst_pkg.metadata.version if tmp_pkg.metadata.version > dst_pkg.metadata.version
@@ -231,7 +239,7 @@ class PackageManagerInstallMixin(object):
tmp_pkg.get_safe_dirname(), tmp_pkg.get_safe_dirname(),
tmp_pkg.metadata.version, tmp_pkg.metadata.version,
) )
if tmp_pkg.metadata.spec.url: if tmp_pkg.metadata.spec.external:
target_dirname = "%s@src-%s" % ( target_dirname = "%s@src-%s" % (
tmp_pkg.get_safe_dirname(), tmp_pkg.get_safe_dirname(),
hashlib.md5( hashlib.md5(
@@ -247,3 +255,20 @@ class PackageManagerInstallMixin(object):
_cleanup_dir(dst_pkg.path) _cleanup_dir(dst_pkg.path)
shutil.move(tmp_pkg.path, dst_pkg.path) shutil.move(tmp_pkg.path, dst_pkg.path)
return PackageSourceItem(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): class PackageManageRegistryMixin(object):
def install_from_registry(self, spec, search_filters=None, silent=False): def install_from_registry(self, spec, search_filters=None, silent=False):
if spec.owner and spec.name and not search_filters: 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: if not package:
raise UnknownPackageError(spec.humanize()) raise UnknownPackageError(spec.humanize())
version = self._pick_best_pkg_version(package["versions"], spec) version = self.pick_best_registry_version(package["versions"], spec)
else: else:
packages = self.search_registry_packages(spec, search_filters) packages = self.search_registry_packages(spec, search_filters)
if not packages: if not packages:
@@ -131,10 +131,33 @@ class PackageManageRegistryMixin(object):
"items" "items"
] ]
def fetch_registry_package(self, owner, name): def fetch_registry_package(self, spec):
return self.get_registry_client_instance().get_package( result = None
self.pkg_type, owner, name 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 @staticmethod
def print_multi_package_issue(packages, spec): def print_multi_package_issue(packages, spec):
@@ -160,7 +183,7 @@ class PackageManageRegistryMixin(object):
def find_best_registry_version(self, packages, spec): def find_best_registry_version(self, packages, spec):
# find compatible version within the latest package versions # find compatible version within the latest package versions
for package in packages: for package in packages:
version = self._pick_best_pkg_version([package["version"]], spec) version = self.pick_best_registry_version([package["version"]], spec)
if version: if version:
return (package, version) return (package, version)
@@ -169,9 +192,13 @@ class PackageManageRegistryMixin(object):
# if the custom version requirements, check ALL package versions # if the custom version requirements, check ALL package versions
for package in packages: for package in packages:
version = self._pick_best_pkg_version( version = self.pick_best_registry_version(
self.fetch_registry_package( self.fetch_registry_package(
package["owner"]["username"], package["name"] PackageSpec(
id=package["id"],
owner=package["owner"]["username"],
name=package["name"],
)
).get("versions"), ).get("versions"),
spec, spec,
) )
@@ -180,11 +207,12 @@ class PackageManageRegistryMixin(object):
time.sleep(1) time.sleep(1)
return None 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 best = None
for version in versions: for version in versions:
semver = PackageMetaData.to_semver(version["name"]) 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 continue
if not any( if not any(
self.is_system_compatible(f.get("system")) for f in version["files"] self.is_system_compatible(f.get("system")) for f in version["files"]

View File

@@ -31,10 +31,7 @@ class PackageManagerUninstallMixin(object):
self.unlock() self.unlock()
def _uninstall(self, pkg, silent=False, skip_dependencies=False): def _uninstall(self, pkg, silent=False, skip_dependencies=False):
if not isinstance(pkg, PackageSourceItem): pkg = self.get_package(pkg)
pkg = (
PackageSourceItem(pkg) if os.path.isdir(pkg) else self.get_package(pkg)
)
if not pkg or not pkg.metadata: if not pkg or not pkg.metadata:
raise UnknownPackageError(pkg) raise UnknownPackageError(pkg)
@@ -73,7 +70,7 @@ class PackageManagerUninstallMixin(object):
if not silent: if not silent:
click.echo("[%s]" % click.style("OK", fg="green")) click.echo("[%s]" % click.style("OK", fg="green"))
return True return pkg
def _uninstall_dependencies(self, pkg, silent=False): def _uninstall_dependencies(self, pkg, silent=False):
assert isinstance(pkg, PackageSourceItem) 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 click
import semantic_version import semantic_version
from platformio import fs, util from platformio import util
from platformio.commands import PlatformioCLI from platformio.commands import PlatformioCLI
from platformio.compat import ci_strings_are_equal
from platformio.package.exception import ManifestException, MissingPackageManifestError from platformio.package.exception import ManifestException, MissingPackageManifestError
from platformio.package.lockfile import LockFile from platformio.package.lockfile import LockFile
from platformio.package.manager._download import PackageManagerDownloadMixin from platformio.package.manager._download import PackageManagerDownloadMixin
from platformio.package.manager._install import PackageManagerInstallMixin 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._registry import PackageManageRegistryMixin
from platformio.package.manager._uninstall import PackageManagerUninstallMixin from platformio.package.manager._uninstall import PackageManagerUninstallMixin
from platformio.package.manager._update import PackageManagerUpdateMixin
from platformio.package.manifest.parser import ManifestParserFactory from platformio.package.manifest.parser import ManifestParserFactory
from platformio.package.meta import ( from platformio.package.meta import (
PackageMetaData, PackageMetaData,
@@ -41,6 +44,8 @@ class BasePackageManager( # pylint: disable=too-many-public-methods
PackageManageRegistryMixin, PackageManageRegistryMixin,
PackageManagerInstallMixin, PackageManagerInstallMixin,
PackageManagerUninstallMixin, PackageManagerUninstallMixin,
PackageManagerUpdateMixin,
PackageManagerLegacyMixin,
): ):
_MEMORY_CACHE = {} _MEMORY_CACHE = {}
@@ -83,10 +88,6 @@ class BasePackageManager( # pylint: disable=too-many-public-methods
return True return True
return util.items_in_list(value, util.get_systype()) 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 @staticmethod
def ensure_dir_exists(path): def ensure_dir_exists(path):
if not os.path.isdir(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") click.secho(str(e), fg="yellow")
raise MissingPackageManifestError(", ".join(self.manifest_names)) raise MissingPackageManifestError(", ".join(self.manifest_names))
def build_legacy_spec(self, pkg_dir): @staticmethod
# find src manifest def generate_rand_version():
src_manifest_name = ".piopkgmanager.json" return datetime.now().strftime("0.0.0+%Y%m%d%H%M%S")
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 build_metadata(self, pkg_dir, spec, vcs_revision=None): def build_metadata(self, pkg_dir, spec, vcs_revision=None):
manifest = self.load_manifest(pkg_dir) manifest = self.load_manifest(pkg_dir)
@@ -192,7 +175,7 @@ class BasePackageManager( # pylint: disable=too-many-public-methods
version=manifest.get("version"), version=manifest.get("version"),
spec=spec, spec=spec,
) )
if not metadata.name or spec.is_custom_name(): if not metadata.name or spec.has_custom_name():
metadata.name = spec.name metadata.name = spec.name
if vcs_revision: if vcs_revision:
metadata.version = "%s+sha.%s" % ( metadata.version = "%s+sha.%s" % (
@@ -203,42 +186,27 @@ class BasePackageManager( # pylint: disable=too-many-public-methods
metadata.version = self.generate_rand_version() metadata.version = self.generate_rand_version()
return metadata 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 get_package(self, spec):
def _ci_strings_are_equal(a, b): if isinstance(spec, PackageSourceItem):
if a == b: return spec
return True
if not a or not b: if not isinstance(spec, PackageSpec) and os.path.isdir(spec):
return False for pkg in self.get_installed():
return a.strip().lower() == b.strip().lower() if spec == pkg.path:
return pkg
return None
spec = self.ensure_spec(spec) spec = self.ensure_spec(spec)
best = None best = None
for pkg in self.get_installed(): for pkg in self.get_installed():
skip_conditions = [ skip_conditions = [
spec.owner spec.owner
and not _ci_strings_are_equal(spec.owner, pkg.metadata.spec.owner), and not ci_strings_are_equal(spec.owner, pkg.metadata.spec.owner),
spec.url and spec.url != pkg.metadata.spec.url, spec.external and spec.url != pkg.metadata.spec.url,
spec.id and spec.id != pkg.metadata.spec.id, spec.id and spec.id != pkg.metadata.spec.id,
not spec.id not spec.id
and not spec.url and not spec.external
and not _ci_strings_are_equal(spec.name, pkg.metadata.name), and not ci_strings_are_equal(spec.name, pkg.metadata.name),
] ]
if any(skip_conditions): if any(skip_conditions):
continue continue

View File

@@ -21,7 +21,7 @@ from platformio.package.meta import PackageSpec, PackageType
from platformio.project.helpers import get_project_global_lib_dir 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): def __init__(self, package_dir=None):
super(LibraryPackageManager, self).__init__( super(LibraryPackageManager, self).__init__(
PackageType.LIBRARY, package_dir or get_project_global_lib_dir() 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 from platformio.project.config import ProjectConfig
class PlatformPackageManager(BasePackageManager): class PlatformPackageManager(BasePackageManager): # pylint: disable=too-many-ancestors
def __init__(self, package_dir=None): def __init__(self, package_dir=None):
self.config = ProjectConfig.get_instance() self.config = ProjectConfig.get_instance()
super(PlatformPackageManager, self).__init__( super(PlatformPackageManager, self).__init__(

View File

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

View File

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

View File

@@ -65,7 +65,44 @@ class PackageType(object):
return None 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 def __init__( # pylint: disable=redefined-builtin,too-many-arguments
self, raw=None, owner=None, id=None, name=None, requirements=None, url=None self, raw=None, owner=None, id=None, name=None, requirements=None, url=None
): ):
@@ -74,6 +111,7 @@ class PackageSpec(object):
self.name = name self.name = name
self._requirements = None self._requirements = None
self.url = url self.url = url
self.raw = raw
if requirements: if requirements:
self.requirements = requirements self.requirements = requirements
self._name_is_custom = False self._name_is_custom = False
@@ -104,6 +142,10 @@ class PackageSpec(object):
"requirements={requirements} url={url}>".format(**self.as_dict()) "requirements={requirements} url={url}>".format(**self.as_dict())
) )
@property
def external(self):
return bool(self.url)
@property @property
def requirements(self): def requirements(self):
return self._requirements return self._requirements
@@ -116,24 +158,24 @@ class PackageSpec(object):
self._requirements = ( self._requirements = (
value value
if isinstance(value, semantic_version.SimpleSpec) if isinstance(value, semantic_version.SimpleSpec)
else semantic_version.SimpleSpec(value) else semantic_version.SimpleSpec(str(value))
) )
def humanize(self): def humanize(self):
result = ""
if self.url: if self.url:
result = self.url result = self.url
elif self.id: elif self.name:
result = "id:%d" % self.id
else:
result = ""
if self.owner: if self.owner:
result = self.owner + "/" result = self.owner + "/"
result += self.name result += self.name
elif self.id:
result = "id:%d" % self.id
if self.requirements: if self.requirements:
result += " @ " + str(self.requirements) result += " @ " + str(self.requirements)
return result return result
def is_custom_name(self): def has_custom_name(self):
return self._name_is_custom return self._name_is_custom
def as_dict(self): def as_dict(self):
@@ -145,6 +187,19 @@ class PackageSpec(object):
url=self.url, 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): def _parse(self, raw):
if raw is None: if raw is None:
return return

View File

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

View File

@@ -13,332 +13,184 @@
# limitations under the License. # limitations under the License.
import json import json
import re import os
from platformio import exception import semantic_version
from platformio.commands import PlatformioCLI
from platformio.commands.lib import cli as cmd_lib
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): def test_saving_deps(clirunner, validate_cliresult, isolated_pio_core, tmpdir_factory):
result = clirunner.invoke(cmd_lib, ["search", "DHT22"]) 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) validate_cliresult(result)
match = re.search(r"Found\s+(\d+)\slibraries:", result.output) aj_pkg_data = regclient.get_package(PackageType.LIBRARY, "bblanchon", "ArduinoJson")
assert int(match.group(1)) > 2 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) validate_cliresult(result)
match = re.search(r"Found\s+(\d+)\slibraries:", result.output) ll_pkg_data = regclient.get_package(
assert int(match.group(1)) > 1 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"],
]
# check external package via Git repo
def test_global_install_registry(clirunner, validate_cliresult, isolated_pio_core):
result = clirunner.invoke( result = clirunner.invoke(
cmd_lib, cmd_lib,
[ [
"-g", "-d",
str(project_dir),
"-e",
"one",
"install", "install",
"64", "https://github.com/OttoWinter/async-mqtt-client.git#v0.8.3 @ 0.8.3",
"ArduinoJson@~5.10.0",
"547@2.2.4",
"AsyncMqttClient@<=0.8.2",
"Adafruit PN532@1.2.0",
], ],
) )
validate_cliresult(result) 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 # test uninstalling
result = clirunner.invoke(cmd_lib, ["-g", "install", "Unknown"]) result = clirunner.invoke(
assert result.exit_code != 0 cmd_lib, ["-d", str(project_dir), "uninstall", "ArduinoJson"]
assert isinstance(result.exception, exception.LibNotFound) )
validate_cliresult(result)
items1 = [d.basename for d in isolated_pio_core.join("lib").listdir()] config = ProjectConfig(os.path.join(str(project_dir), "platformio.ini"))
items2 = [ assert len(config.get("env:one", "lib_deps")) == 2
"ArduinoJson", assert len(config.get("env:two", "lib_deps")) == 1
"ArduinoJson@5.10.1", assert config.get("env:one", "lib_deps") == [
"NeoPixelBus", "mbed-sam-grove/LinkedList@%s" % ll_pkg_data["version"]["name"],
"AsyncMqttClient", "https://github.com/OttoWinter/async-mqtt-client.git#v0.8.3 @ 0.8.3",
"ESPAsyncTCP",
"AsyncTCP",
"Adafruit PN532",
"Adafruit BusIO",
] ]
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( result = clirunner.invoke(
cmd_lib, cmd_lib,
[ [
"-g", "-d",
"install", str(storage_dir),
"https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip", "update",
"https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip@5.8.2", "--dry-run",
"SomeLib=http://dl.platformio.org/libraries/archives/0/9540.tar.gz", "--json-output",
"https://github.com/Pedroalbuquerque/ESP32WebServer/archive/master.zip", "ArduinoJson @ ^5",
], ],
) )
validate_cliresult(result) validate_cliresult(result)
outdated = json.loads(result.stdout)
# incorrect requirements 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( result = clirunner.invoke(
cmd_lib, cmd_lib, ["-d", str(storage_dir), "update", "--silent", "ArduinoJson @ ^5.10.1"]
[
"-g",
"install",
"https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip@1.2.3",
],
) )
assert result.exit_code != 0 validate_cliresult(result)
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):
result = clirunner.invoke( result = clirunner.invoke(
cmd_lib, cmd_lib, ["-d", str(storage_dir), "list", "--json-output"]
[
"-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",
],
) )
validate_cliresult(result) validate_cliresult(result)
items1 = [d.basename for d in isolated_pio_core.join("lib").listdir()] items = json.loads(result.stdout)
items2 = [ assert len(items) == 2
"PJON", assert items[0]["version"] == "5.13.4"
"PJON@src-79de467ebe19de18287becff0a1fb42d", assert items[1]["version"] == "0.5.4"
"ArduinoJson@src-69ebddd821f771debe7ee734d3c7fa81",
"rs485-nodeproto",
"platformio-libmirror",
"PubSubClient",
]
assert set(items1) >= set(items2)
# Check incompatible
def test_install_duplicates(clirunner, validate_cliresult, without_internet):
# registry
result = clirunner.invoke( result = clirunner.invoke(
cmd_lib, cmd_lib, ["-d", str(storage_dir), "update", "--dry-run", "ArduinoJson @ ^5"]
["-g", "install", "http://dl.platformio.org/libraries/archives/0/9540.tar.gz"],
) )
validate_cliresult(result) validate_cliresult(result)
assert "is already installed" in result.output assert "Incompatible" in result.stdout
# 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())

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 time
import pytest import pytest
import semantic_version
from platformio import fs, util from platformio import fs, util
from platformio.package.exception import ( 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 lm.get_package("OneWire").metadata.version.major >= 2
assert len(lm.get_installed()) == 6 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 # Tools
tm = ToolPackageManager(str(tmpdir_factory.mktemp("tool-storage"))) tm = ToolPackageManager(str(tmpdir_factory.mktemp("tool-storage")))
pkg = tm.install("platformio/tool-stlink @ ~1.10400.0", silent=True) 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.install("AsyncMqttClient-esphome @ 0.8.4", silent=True)
assert lm.uninstall("AsyncMqttClient-esphome", silent=True) assert lm.uninstall("AsyncMqttClient-esphome", silent=True)
assert len(lm.get_installed()) == 0 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 jsondiff
import semantic_version 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(): def test_spec_owner():
@@ -45,9 +65,16 @@ def test_spec_name():
def test_spec_requirements(): def test_spec_requirements():
assert PackageSpec("foo@1.2.3") == PackageSpec(name="foo", requirements="1.2.3") 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("bar @ ^1.2.3") == PackageSpec(name="bar", requirements="^1.2.3")
assert PackageSpec("13 @ ~2.0") == PackageSpec(id=13, requirements="~2.0") 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") spec = PackageSpec("id=20 @ !=1.2.3,<2.0")
assert not spec.external
assert isinstance(spec.requirements, semantic_version.SimpleSpec) assert isinstance(spec.requirements, semantic_version.SimpleSpec)
assert semantic_version.Version("1.3.0-beta.1") in spec.requirements assert semantic_version.Version("1.3.0-beta.1") in spec.requirements
assert spec == PackageSpec(id=20, requirements="!=1.2.3,<2.0") assert spec == PackageSpec(id=20, requirements="!=1.2.3,<2.0")
@@ -88,7 +115,8 @@ def test_spec_external_urls():
"Custom-Name=" "Custom-Name="
"https://github.com/platformio/platformio-core/archive/develop.tar.gz@4.4.0" "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.name == "Custom-Name"
assert spec == PackageSpec( assert spec == PackageSpec(
url="https://github.com/platformio/platformio-core/archive/develop.tar.gz", 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(): def test_metadata_as_dict():
metadata = PackageMetaData(PackageType.LIBRARY, "foo", "1.2.3") metadata = PackageMetaData(PackageType.LIBRARY, "foo", "1.2.3")
# test setter # test setter

View File

@@ -88,7 +88,9 @@ def test_check_and_update_libraries(clirunner, isolated_pio_core, validate_clire
validate_cliresult(result) validate_cliresult(result)
assert "There are the new updates for libraries (ArduinoJson)" in result.output assert "There are the new updates for libraries (ArduinoJson)" in result.output
assert "Please wait while updating libraries" 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 # check updated version
result = clirunner.invoke(cli_pio, ["lib", "-g", "list", "--json-output"]) result = clirunner.invoke(cli_pio, ["lib", "-g", "list", "--json-output"])