diff --git a/HISTORY.rst b/HISTORY.rst index e57e3fe6..b16ba0ff 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -8,7 +8,7 @@ PlatformIO Core 5 **A professional collaborative platform for embedded development** -5.3.0 (2022-02-??) +5.3.0 (2022-??-??) ~~~~~~~~~~~~~~~~~~ * **Package Management** @@ -17,7 +17,9 @@ PlatformIO Core 5 * `pio pkg exec `_ - run command from package tool (`issue #4163 `_) * `pio pkg install `_ - install the project dependencies or custom packages - * `pio pkg outdated `__ - check for project outdated packages + * `pio pkg outdated `__ - check for project outdated packages + * `pio pkg update `__ - update the project dependencies or custom packages + * `pio pkg uninstall `_ - uninstall the project dependencies or custom packages - Added support for dependencies declared in a "tool" type package - Ignore files according to the patterns declared in ".gitignore" when using `pio package pack `__ command (`issue #4188 `_) diff --git a/docs b/docs index 7aa5b899..cbf179f8 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit 7aa5b8996381e98c0824170378614fa3369a243e +Subproject commit cbf179f826faa674846da782cbf3917a9f0a475f diff --git a/platformio/commands/lib/command.py b/platformio/commands/lib/command.py index 5c5553e6..008da408 100644 --- a/platformio/commands/lib/command.py +++ b/platformio/commands/lib/command.py @@ -237,8 +237,13 @@ def lib_uninstall(ctx, libraries, save, silent): def lib_update( # pylint: disable=too-many-arguments ctx, libraries, only_check, dry_run, silent, json_output ): - storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY] only_check = dry_run or only_check + if only_check and not json_output: + raise exception.UserSideException( + "This command is deprecated, please use `pio pkg outdated` instead" + ) + + storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY] json_result = {} for storage_dir in storage_dirs: if not json_output: @@ -278,7 +283,7 @@ def lib_update( # pylint: disable=too-many-arguments None if isinstance(library, PackageItem) else PackageSpec(library) ) try: - lm.update(library, to_spec=to_spec, only_check=only_check) + lm.update(library, to_spec=to_spec) except UnknownPackageError as e: if library not in lib_deps: raise e @@ -529,7 +534,7 @@ def lib_show(library, json_output): @click.argument("config_url") def lib_register(config_url): # pylint: disable=unused-argument raise exception.UserSideException( - "This command is deprecated. Please use `pio package publish` command." + "This command is deprecated. Please use `pio pkg publish` command." ) diff --git a/platformio/commands/pkg.py b/platformio/commands/pkg.py index 7e8ba0d0..1ec965ba 100644 --- a/platformio/commands/pkg.py +++ b/platformio/commands/pkg.py @@ -21,6 +21,7 @@ from platformio.package.commands.pack import package_pack_cmd from platformio.package.commands.publish import package_publish_cmd from platformio.package.commands.uninstall import package_uninstall_cmd from platformio.package.commands.unpublish import package_unpublish_cmd +from platformio.package.commands.update import package_update_cmd @click.group( @@ -28,11 +29,12 @@ from platformio.package.commands.unpublish import package_unpublish_cmd commands=[ package_exec_cmd, package_install_cmd, - package_uninstall_cmd, package_outdated_cmd, package_pack_cmd, package_publish_cmd, + package_uninstall_cmd, package_unpublish_cmd, + package_update_cmd, ], short_help="Package Manager", ) diff --git a/platformio/commands/platform.py b/platformio/commands/platform.py index 238b1f01..02e096a4 100644 --- a/platformio/commands/platform.py +++ b/platformio/commands/platform.py @@ -273,8 +273,13 @@ def platform_uninstall(platforms): @click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting") @click.option("--json-output", is_flag=True) def platform_update( # pylint: disable=too-many-locals, too-many-arguments - platforms, only_packages, only_check, dry_run, silent, json_output + platforms, only_check, dry_run, silent, json_output, **_ ): + if only_check and not json_output: + raise UserSideException( + "This command is deprecated, please use `pio pkg outdated` instead" + ) + pm = PlatformPackageManager() pm.set_log_level(logging.WARN if silent else logging.DEBUG) platforms = platforms or pm.get_installed() @@ -322,7 +327,7 @@ def platform_update( # pylint: disable=too-many-locals, too-many-arguments ) ) click.echo("--------") - pm.update(platform, only_packages=only_packages, only_check=only_check) + pm.update(platform) click.echo() return True diff --git a/platformio/commands/update.py b/platformio/commands/update.py index ff88723e..7d4e64b5 100644 --- a/platformio/commands/update.py +++ b/platformio/commands/update.py @@ -42,7 +42,8 @@ def cli(ctx, core_packages, only_check, dry_run): only_check = dry_run or only_check - update_core_packages(only_check) + if not only_check: + update_core_packages() if core_packages: return diff --git a/platformio/commands/upgrade.py b/platformio/commands/upgrade.py index 25e5bd01..228f996c 100644 --- a/platformio/commands/upgrade.py +++ b/platformio/commands/upgrade.py @@ -22,6 +22,7 @@ import click from platformio import VERSION, __version__, app, exception from platformio.clients.http import fetch_remote_content from platformio.compat import IS_WINDOWS +from platformio.package.manager.core import update_core_packages from platformio.proc import exec_command, get_pythonexe_path from platformio.project.helpers import get_project_cache_dir @@ -29,6 +30,7 @@ from platformio.project.helpers import get_project_cache_dir @click.command("upgrade", short_help="Upgrade PlatformIO to the latest version") @click.option("--dev", is_flag=True, help="Use development branch") def cli(dev): + update_core_packages() if not dev and __version__ == get_latest_version(): return click.secho( "You're up-to-date!\nPlatformIO %s is currently the " diff --git a/platformio/maintenance.py b/platformio/maintenance.py index b1e0582b..dccd8086 100644 --- a/platformio/maintenance.py +++ b/platformio/maintenance.py @@ -150,13 +150,10 @@ def after_upgrade(ctx): return else: click.secho("Please wait while upgrading PlatformIO...", fg="yellow") - try: - cleanup_content_cache("http") - except: # pylint: disable=bare-except - pass # Update PlatformIO's Core packages - update_core_packages(silent=True) + cleanup_content_cache("http") + update_core_packages() u = Upgrader(last_version, __version__) if u.run(ctx): @@ -219,7 +216,7 @@ def check_platformio_upgrade(): http.ensure_internet_on(raise_exception=True) # Update PlatformIO Core packages - update_core_packages(silent=True) + update_core_packages() latest_version = get_latest_version() if pepver_to_semver(latest_version) <= pepver_to_semver(__version__): diff --git a/platformio/package/commands/update.py b/platformio/package/commands/update.py new file mode 100644 index 00000000..378f9a1b --- /dev/null +++ b/platformio/package/commands/update.py @@ -0,0 +1,252 @@ +# Copyright (c) 2014-present PlatformIO +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import os + +import click + +from platformio import fs +from platformio.package.manager.library import LibraryPackageManager +from platformio.package.manager.platform import PlatformPackageManager +from platformio.package.manager.tool import ToolPackageManager +from platformio.package.meta import PackageSpec +from platformio.project.config import ProjectConfig +from platformio.project.savedeps import pkg_to_save_spec, save_project_dependencies + + +@click.command( + "update", short_help="Update the project dependencies or custom packages" +) +@click.option( + "-d", + "--project-dir", + default=os.getcwd, + type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True), +) +@click.option("-e", "--environment", "environments", multiple=True) +@click.option("-p", "--platform", "platforms", multiple=True) +@click.option("-t", "--tool", "tools", multiple=True) +@click.option("-l", "--library", "libraries", multiple=True) +@click.option( + "--no-save", + is_flag=True, + help="Prevent saving specified packages to `platformio.ini`", +) +@click.option("--skip-dependencies", is_flag=True, help="Skip package dependencies") +@click.option("-g", "--global", is_flag=True, help="Update global packages") +@click.option( + "--storage-dir", + default=None, + type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True), + help="Custom Package Manager storage for global packages", +) +@click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting") +def package_update_cmd(**options): + if options.get("global"): + update_global_dependencies(options) + else: + update_project_dependencies(options) + + +def update_global_dependencies(options): + pm = PlatformPackageManager(options.get("storage_dir")) + tm = ToolPackageManager(options.get("storage_dir")) + lm = LibraryPackageManager(options.get("storage_dir")) + for obj in (pm, tm, lm): + obj.set_log_level(logging.WARN if options.get("silent") else logging.DEBUG) + for spec in options.get("platforms"): + pm.update( + from_spec=spec, + to_spec=spec, + skip_dependencies=options.get("skip_dependencies"), + ) + for spec in options.get("tools"): + tm.update( + from_spec=spec, + to_spec=spec, + skip_dependencies=options.get("skip_dependencies"), + ) + for spec in options.get("libraries", []): + lm.update( + from_spec=spec, + to_spec=spec, + skip_dependencies=options.get("skip_dependencies"), + ) + + +def update_project_dependencies(options): + environments = options["environments"] + with fs.cd(options["project_dir"]): + config = ProjectConfig.get_instance() + config.validate(environments) + for env in config.envs(): + if environments and env not in environments: + continue + if not options["silent"]: + click.echo( + "Resolving %s environment packages..." % click.style(env, fg="cyan") + ) + already_up_to_date = not update_project_env_dependencies(env, options) + if not options["silent"] and already_up_to_date: + click.secho("Already up-to-date.", fg="green") + + +def update_project_env_dependencies(project_env, options=None): + options = options or {} + updated_conds = [] + # custom platforms + if options.get("platforms"): + updated_conds.append(_update_project_env_custom_platforms(project_env, options)) + # custom tools + if options.get("tools"): + updated_conds.append(_update_project_env_custom_tools(project_env, options)) + # custom ibraries + if options.get("libraries"): + updated_conds.append(_update_project_env_custom_libraries(project_env, options)) + # declared dependencies + if not updated_conds: + updated_conds = [ + _update_project_env_platform(project_env, options), + _update_project_env_libraries(project_env, options), + ] + return any(updated_conds) + + +def _update_project_env_platform(project_env, options): + config = ProjectConfig.get_instance() + pm = PlatformPackageManager() + if options.get("silent"): + pm.set_log_level(logging.WARN) + spec = config.get(f"env:{project_env}", "platform") + if not spec: + return None + cur_pkg = pm.get_package(spec) + if not cur_pkg: + return None + new_pkg = PlatformPackageManager().update( + cur_pkg, + to_spec=spec, + project_env=project_env, + skip_dependencies=options.get("skip_dependencies"), + ) + return cur_pkg != new_pkg + + +def _update_project_env_custom_platforms(project_env, options): + already_up_to_date = True + pm = PlatformPackageManager() + if not options.get("silent"): + pm.set_log_level(logging.DEBUG) + for spec in options.get("platforms"): + cur_pkg = pm.get_package(spec) + new_pkg = pm.update( + cur_pkg, + to_spec=spec, + project_env=project_env, + skip_dependencies=options.get("skip_dependencies"), + ) + if cur_pkg != new_pkg: + already_up_to_date = False + return not already_up_to_date + + +def _update_project_env_custom_tools(project_env, options): + already_up_to_date = True + tm = ToolPackageManager() + if not options.get("silent"): + tm.set_log_level(logging.DEBUG) + + specs_to_save = [] + for tool in options.get("tools"): + spec = PackageSpec(tool) + cur_pkg = tm.get_package(spec) + new_pkg = tm.update( + cur_pkg, + to_spec=spec, + skip_dependencies=options.get("skip_dependencies"), + ) + if cur_pkg != new_pkg: + already_up_to_date = False + specs_to_save.append(pkg_to_save_spec(new_pkg, spec)) + + if not options.get("no_save") and specs_to_save: + save_project_dependencies( + os.getcwd(), + specs_to_save, + scope="platform_packages", + action="add", + environments=[project_env], + ) + + return not already_up_to_date + + +def _update_project_env_libraries(project_env, options): + already_up_to_date = True + config = ProjectConfig.get_instance() + lm = LibraryPackageManager( + os.path.join(config.get("platformio", "libdeps_dir"), project_env) + ) + if options.get("silent"): + lm.set_log_level(logging.WARN) + for library in config.get(f"env:{project_env}", "lib_deps"): + spec = PackageSpec(library) + # skip built-in dependencies + if not spec.external and not spec.owner: + continue + cur_pkg = lm.get_package(spec) + if cur_pkg: + new_pkg = lm.update( + cur_pkg, + to_spec=spec, + skip_dependencies=options.get("skip_dependencies"), + ) + if cur_pkg != new_pkg: + already_up_to_date = False + return not already_up_to_date + + +def _update_project_env_custom_libraries(project_env, options): + already_up_to_date = True + config = ProjectConfig.get_instance() + lm = LibraryPackageManager( + os.path.join(config.get("platformio", "libdeps_dir"), project_env) + ) + if not options.get("silent"): + lm.set_log_level(logging.DEBUG) + + specs_to_save = [] + for library in options.get("libraries") or []: + spec = PackageSpec(library) + cur_pkg = lm.get_package(spec) + new_pkg = lm.update( + cur_pkg, + to_spec=spec, + skip_dependencies=options.get("skip_dependencies"), + ) + if cur_pkg != new_pkg: + already_up_to_date = False + specs_to_save.append(pkg_to_save_spec(new_pkg, spec)) + + if not options.get("no_save") and specs_to_save: + save_project_dependencies( + os.getcwd(), + specs_to_save, + scope="lib_deps", + action="add", + environments=[project_env], + ) + + return not already_up_to_date diff --git a/platformio/package/manager/_update.py b/platformio/package/manager/_update.py index 4815f349..d2a0b6eb 100644 --- a/platformio/package/manager/_update.py +++ b/platformio/package/manager/_update.py @@ -12,12 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging import os import click -from platformio.clients.http import ensure_internet_on from platformio.package.exception import UnknownPackageError from platformio.package.meta import PackageItem, PackageOutdatedResult, PackageSpec from platformio.package.vcsclient import VCSBaseException, VCSClientFactory @@ -26,9 +24,11 @@ from platformio.package.vcsclient import VCSBaseException, VCSClientFactory class PackageManagerUpdateMixin(object): def outdated(self, pkg, spec=None): assert isinstance(pkg, PackageItem) - assert not spec or isinstance(spec, PackageSpec) assert pkg.metadata + if spec and not isinstance(spec, PackageSpec): + spec = PackageSpec(spec) + if not os.path.isdir(pkg.path): return PackageOutdatedResult(current=pkg.metadata.version) @@ -82,82 +82,35 @@ class PackageManagerUpdateMixin(object): self, from_spec, to_spec=None, - only_check=False, - show_incompatible=True, + skip_dependencies=False, ): pkg = self.get_package(from_spec) if not pkg or not pkg.metadata: raise UnknownPackageError(from_spec) - silent = not self.log.isEnabledFor(logging.INFO) - if not silent: - click.echo( - "{} {:<45} {:<35}".format( - "Checking" if only_check else "Updating", - click.style(pkg.metadata.spec.humanize(), fg="cyan"), - "%s @ %s" % (pkg.metadata.version, to_spec.requirements) - if to_spec and to_spec.requirements - else str(pkg.metadata.version), - ), - nl=False, - ) - if not ensure_internet_on(): - if not silent: - click.echo("[%s]" % (click.style("Off-line", fg="yellow"))) - return pkg - outdated = self.outdated(pkg, to_spec) - if not silent: - self.print_outdated_state(outdated, only_check, show_incompatible) - - if only_check or not outdated.is_outdated(allow_incompatible=False): + if not outdated.is_outdated(allow_incompatible=False): + self.log.debug( + click.style( + "{name} @ {version} is already up-to-date".format( + **pkg.metadata.as_dict() + ), + fg="yellow", + ) + ) return pkg + self.log.info( + "Updating %s @ %s" + % (click.style(pkg.metadata.name, fg="cyan"), pkg.metadata.version) + ) try: self.lock() - return self._update(pkg, outdated) + return self._update(pkg, outdated, skip_dependencies) finally: self.unlock() - @staticmethod - def print_outdated_state(outdated, only_check, show_incompatible): - if outdated.detached: - return click.echo("[%s]" % (click.style("Detached", fg="yellow"))) - - if ( - not outdated.latest - or outdated.current == outdated.latest - or (not show_incompatible and outdated.current == outdated.wanted) - ): - 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")) - ) - - if only_check: - return click.echo( - "[%s]" - % ( - click.style( - "Outdated %s" % str(outdated.wanted or outdated.latest), - fg="red", - ) - ) - ) - - return click.echo( - "[%s]" - % ( - click.style( - "Updating to %s" % str(outdated.wanted or outdated.latest), - fg="green", - ) - ) - ) - - def _update(self, pkg, outdated): + def _update(self, pkg, outdated, skip_dependencies=False): if pkg.metadata.spec.external: vcs = VCSClientFactory.new(pkg.path, pkg.metadata.spec.url) assert vcs.update() @@ -165,23 +118,15 @@ class PackageManagerUpdateMixin(object): pkg.dump_meta() return pkg - new_pkg = self.install( + # uninstall existing version + self.uninstall(pkg, skip_dependencies=True) + + return self.install( PackageSpec( id=pkg.metadata.spec.id, owner=pkg.metadata.spec.owner, name=pkg.metadata.spec.name, requirements=outdated.wanted or outdated.latest, - ) + ), + skip_dependencies=skip_dependencies, ) - 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, skip_dependencies=True) - return new_pkg diff --git a/platformio/package/manager/core.py b/platformio/package/manager/core.py index f554dd6a..d9694217 100644 --- a/platformio/package/manager/core.py +++ b/platformio/package/manager/core.py @@ -55,17 +55,15 @@ def get_core_package_dir(name, auto_install=True): return pm.get_package(spec).path -def update_core_packages(only_check=False, silent=False): +def update_core_packages(): pm = ToolPackageManager() for name, requirements in __core_packages__.items(): spec = PackageSpec(owner="platformio", name=name, requirements=requirements) - pkg = pm.get_package(spec) - if not pkg: - continue - if not silent or pm.outdated(pkg, spec).is_outdated(): - pm.update(pkg, spec, only_check=only_check) - if not only_check: - remove_unnecessary_core_packages() + try: + pm.update(spec, spec) + except UnknownPackageError: + pass + remove_unnecessary_core_packages() return True diff --git a/platformio/package/manager/platform.py b/platformio/package/manager/platform.py index 71cd5ddd..92dc6d61 100644 --- a/platformio/package/manager/platform.py +++ b/platformio/package/manager/platform.py @@ -85,45 +85,28 @@ class PlatformPackageManager(BasePackageManager): # pylint: disable=too-many-an p.on_uninstalled() return pkg - def update( # pylint: disable=arguments-differ, too-many-arguments + def update( # pylint: disable=arguments-differ self, from_spec, to_spec=None, - only_check=False, - show_incompatible=True, - only_packages=False, + skip_dependencies=False, + project_env=None, ): pkg = self.get_package(from_spec) if not pkg or not pkg.metadata: raise UnknownPackageError(from_spec) + pkg = super(PlatformPackageManager, self).update( + from_spec, + to_spec, + ) p = PlatformFactory.new(pkg) # set logging level for underlying tool manager p.pm.set_log_level(self.log.getEffectiveLevel()) - pkgs_before = [item.metadata.name for item in p.get_installed_packages()] - - new_pkg = None - missed_pkgs = set() - if not only_packages: - new_pkg = super(PlatformPackageManager, self).update( - from_spec, - to_spec, - only_check=only_check, - show_incompatible=show_incompatible, - ) - p = PlatformFactory.new(new_pkg) - missed_pkgs = set(pkgs_before) & set(p.packages) - missed_pkgs -= set( - item.metadata.name for item in p.get_installed_packages() - ) - - p.update_packages(only_check) - - if missed_pkgs: - p.install_packages( - with_packages=list(missed_pkgs), skip_default_package=True - ) - - return new_pkg or pkg + if project_env: + p.configure_project_packages(project_env) + if not skip_dependencies: + p.update_packages() + return pkg @util.memoized(expire="5s") def get_installed_boards(self): diff --git a/tests/commands/pkg/test_update.py b/tests/commands/pkg/test_update.py new file mode 100644 index 00000000..c70e2b25 --- /dev/null +++ b/tests/commands/pkg/test_update.py @@ -0,0 +1,356 @@ +# Copyright (c) 2014-present PlatformIO +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# pylint: disable=unused-argument + +import os + +from platformio import fs +from platformio.package.commands.install import package_install_cmd +from platformio.package.commands.update import package_update_cmd +from platformio.package.exception import UnknownPackageError +from platformio.package.manager.library import LibraryPackageManager +from platformio.package.manager.platform import PlatformPackageManager +from platformio.package.manager.tool import ToolPackageManager +from platformio.package.meta import PackageSpec +from platformio.project.config import ProjectConfig + +PROJECT_OUTDATED_CONFIG_TPL = """ +[env:devkit] +platform = platformio/atmelavr@^2 +framework = arduino +board = attiny88 +lib_deps = milesburton/DallasTemperature@~3.8.0 +""" + +PROJECT_UPDATED_CONFIG_TPL = """ +[env:devkit] +platform = platformio/atmelavr@<4 +framework = arduino +board = attiny88 +lib_deps = milesburton/DallasTemperature@^3.8.0 +""" + + +def pkgs_to_specs(pkgs): + return [ + PackageSpec(name=pkg.metadata.name, requirements=pkg.metadata.version) + for pkg in pkgs + ] + + +def test_global_packages( + clirunner, validate_cliresult, func_isolated_pio_core, tmp_path +): + # libraries + result = clirunner.invoke( + package_install_cmd, + ["--global", "-l", "bblanchon/ArduinoJson@^5"], + ) + validate_cliresult(result) + assert pkgs_to_specs(LibraryPackageManager().get_installed()) == [ + PackageSpec("ArduinoJson@5.13.4") + ] + # update to the latest version + result = clirunner.invoke( + package_update_cmd, + ["--global", "-l", "bblanchon/ArduinoJson"], + ) + validate_cliresult(result) + pkgs = LibraryPackageManager().get_installed() + assert len(pkgs) == 1 + assert pkgs[0].metadata.version.major > 5 + # custom storage + storage_dir = tmp_path / "custom_lib_storage" + storage_dir.mkdir() + result = clirunner.invoke( + package_install_cmd, + [ + "--global", + "--storage-dir", + str(storage_dir), + "-l", + "bblanchon/ArduinoJson@^5", + ], + ) + validate_cliresult(result) + assert pkgs_to_specs(LibraryPackageManager(storage_dir).get_installed()) == [ + PackageSpec("ArduinoJson@5.13.4") + ] + # update to the latest version + result = clirunner.invoke( + package_update_cmd, + ["--global", "--storage-dir", str(storage_dir), "-l", "bblanchon/ArduinoJson"], + ) + validate_cliresult(result) + pkgs = LibraryPackageManager(storage_dir).get_installed() + assert len(pkgs) == 1 + assert pkgs[0].metadata.version.major > 5 + + # tools + result = clirunner.invoke( + package_install_cmd, + ["--global", "-t", "platformio/framework-arduino-avr-attiny@~1.4"], + ) + validate_cliresult(result) + assert pkgs_to_specs(ToolPackageManager().get_installed()) == [ + PackageSpec("framework-arduino-avr-attiny@1.4.1") + ] + # update to the latest version + result = clirunner.invoke( + package_update_cmd, + ["--global", "-t", "platformio/framework-arduino-avr-attiny@^1"], + ) + validate_cliresult(result) + pkgs = ToolPackageManager().get_installed() + assert len(pkgs) == 1 + assert pkgs[0].metadata.version.major == 1 + assert pkgs[0].metadata.version.minor > 4 + + # platforms + result = clirunner.invoke( + package_install_cmd, + ["--global", "-p", "platformio/atmelavr@^2", "--skip-dependencies"], + ) + validate_cliresult(result) + assert pkgs_to_specs(PlatformPackageManager().get_installed()) == [ + PackageSpec("atmelavr@2.2.0") + ] + # update to the latest version + result = clirunner.invoke( + package_update_cmd, + ["--global", "-p", "platformio/atmelavr", "--skip-dependencies"], + ) + validate_cliresult(result) + pkgs = PlatformPackageManager().get_installed() + assert len(pkgs) == 1 + assert pkgs[0].metadata.version.major > 2 + + # update unknown package + result = clirunner.invoke( + package_update_cmd, + ["--global", "-l", "platformio/unknown_package_for_update"], + ) + assert isinstance(result.exception, UnknownPackageError) + + +def test_project(clirunner, validate_cliresult, isolated_pio_core, tmp_path): + project_dir = tmp_path / "project" + project_dir.mkdir() + (project_dir / "platformio.ini").write_text(PROJECT_OUTDATED_CONFIG_TPL) + result = clirunner.invoke( + package_install_cmd, + ["-d", str(project_dir)], + ) + validate_cliresult(result) + with fs.cd(str(project_dir)): + config = ProjectConfig() + lm = LibraryPackageManager( + os.path.join(config.get("platformio", "libdeps_dir"), "devkit") + ) + assert pkgs_to_specs(lm.get_installed()) == [ + PackageSpec("DallasTemperature@3.8.1"), + PackageSpec("OneWire@2.3.6"), + ] + assert pkgs_to_specs(PlatformPackageManager().get_installed()) == [ + PackageSpec("atmelavr@2.2.0") + ] + assert pkgs_to_specs(ToolPackageManager().get_installed()) == [ + PackageSpec("framework-arduino-avr-attiny@1.3.2"), + PackageSpec("toolchain-atmelavr@1.50400.190710"), + ] + assert config.get("env:devkit", "lib_deps") == [ + "milesburton/DallasTemperature@~3.8.0" + ] + + # update packages + (project_dir / "platformio.ini").write_text(PROJECT_UPDATED_CONFIG_TPL) + result = clirunner.invoke(package_update_cmd) + validate_cliresult(result) + config = ProjectConfig() + lm = LibraryPackageManager( + os.path.join(config.get("platformio", "libdeps_dir"), "devkit") + ) + pkgs = PlatformPackageManager().get_installed() + assert len(pkgs) == 1 + assert pkgs[0].metadata.name == "atmelavr" + assert pkgs[0].metadata.version.major == 3 + assert pkgs_to_specs(lm.get_installed()) == [ + PackageSpec("DallasTemperature@3.9.1"), + PackageSpec("OneWire@2.3.6"), + ] + assert pkgs_to_specs(ToolPackageManager().get_installed()) == [ + PackageSpec("framework-arduino-avr-attiny@1.3.2"), + PackageSpec("toolchain-atmelavr@1.70300.191015"), + PackageSpec("toolchain-atmelavr@1.50400.190710"), + ] + assert config.get("env:devkit", "lib_deps") == [ + "milesburton/DallasTemperature@^3.8.0" + ] + + # update again + result = clirunner.invoke(package_update_cmd) + validate_cliresult(result) + assert "Already up-to-date." in result.output + + # update again in the silent ,pde + result = clirunner.invoke(package_update_cmd, ["--silent"]) + validate_cliresult(result) + assert not result.output + + +def test_custom_project_libraries( + clirunner, validate_cliresult, isolated_pio_core, tmp_path +): + project_dir = tmp_path / "project" + project_dir.mkdir() + (project_dir / "platformio.ini").write_text(PROJECT_OUTDATED_CONFIG_TPL) + spec = "milesburton/DallasTemperature@~3.8.0" + result = clirunner.invoke( + package_install_cmd, + ["-d", str(project_dir), "-e", "devkit", "-l", spec], + ) + validate_cliresult(result) + with fs.cd(str(project_dir)): + config = ProjectConfig() + assert config.get("env:devkit", "lib_deps") == [spec] + lm = LibraryPackageManager( + os.path.join(config.get("platformio", "libdeps_dir"), "devkit") + ) + assert pkgs_to_specs(lm.get_installed()) == [ + PackageSpec("DallasTemperature@3.8.1"), + PackageSpec("OneWire@2.3.6"), + ] + # update package + result = clirunner.invoke( + package_update_cmd, + ["-e", "devkit", "-l", "milesburton/DallasTemperature@^3.8.0"], + ) + assert ProjectConfig().get("env:devkit", "lib_deps") == [ + "milesburton/DallasTemperature@^3.8.0" + ] + # try again + result = clirunner.invoke( + package_update_cmd, + ["-e", "devkit", "-l", "milesburton/DallasTemperature@^3.8.0"], + ) + validate_cliresult(result) + assert "Already up-to-date." in result.output + + # install library without saving to config + result = clirunner.invoke( + package_update_cmd, + ["-e", "devkit", "-l", "milesburton/DallasTemperature@^3", "--no-save"], + ) + validate_cliresult(result) + assert "Already up-to-date." in result.output + config = ProjectConfig() + lm = LibraryPackageManager( + os.path.join(config.get("platformio", "libdeps_dir"), "devkit") + ) + assert pkgs_to_specs(lm.get_installed()) == [ + PackageSpec("DallasTemperature@3.9.1"), + PackageSpec("OneWire@2.3.6"), + ] + assert config.get("env:devkit", "lib_deps") == [ + "milesburton/DallasTemperature@^3.8.0" + ] + + # unknown libraries + result = clirunner.invoke( + package_update_cmd, ["-l", "platformio/unknown_library"] + ) + assert isinstance(result.exception, UnknownPackageError) + + +def test_custom_project_tools( + clirunner, validate_cliresult, func_isolated_pio_core, tmp_path +): + project_dir = tmp_path / "project" + project_dir.mkdir() + (project_dir / "platformio.ini").write_text(PROJECT_OUTDATED_CONFIG_TPL) + spec = "toolchain-atmelavr@~1.50400.0" + result = clirunner.invoke( + package_install_cmd, + ["-d", str(project_dir), "-e", "devkit", "-t", spec], + ) + validate_cliresult(result) + with fs.cd(str(project_dir)): + assert ProjectConfig().get("env:devkit", "platform_packages") == [ + "platformio/toolchain-atmelavr@~1.50400.0" + ] + assert pkgs_to_specs(ToolPackageManager().get_installed()) == [ + PackageSpec("toolchain-atmelavr@1.50400.190710") + ] + result = clirunner.invoke( + package_update_cmd, + ["-e", "devkit", "-t", "toolchain-atmelavr@^1"], + ) + validate_cliresult(result) + assert ProjectConfig().get("env:devkit", "platform_packages") == [ + "platformio/toolchain-atmelavr@^1" + ] + assert pkgs_to_specs(ToolPackageManager().get_installed()) == [ + PackageSpec("toolchain-atmelavr@1.70300.191015") + ] + + # install without saving to config + result = clirunner.invoke( + package_update_cmd, + ["-e", "devkit", "-t", "toolchain-atmelavr@~1.70300.191015", "--no-save"], + ) + validate_cliresult(result) + assert "Already up-to-date." in result.output + assert ProjectConfig().get("env:devkit", "platform_packages") == [ + "platformio/toolchain-atmelavr@^1" + ] + + # unknown tool + result = clirunner.invoke(package_update_cmd, ["-t", "platformio/unknown_tool"]) + assert isinstance(result.exception, UnknownPackageError) + + +def test_custom_project_platforms( + clirunner, validate_cliresult, func_isolated_pio_core, tmp_path +): + project_dir = tmp_path / "project" + project_dir.mkdir() + (project_dir / "platformio.ini").write_text(PROJECT_OUTDATED_CONFIG_TPL) + spec = "atmelavr@^2" + result = clirunner.invoke( + package_install_cmd, + ["-d", str(project_dir), "-e", "devkit", "-p", spec, "--skip-dependencies"], + ) + validate_cliresult(result) + with fs.cd(str(project_dir)): + assert pkgs_to_specs(PlatformPackageManager().get_installed()) == [ + PackageSpec("atmelavr@2.2.0") + ] + assert ProjectConfig().get("env:devkit", "platform") == "platformio/atmelavr@^2" + + # update + result = clirunner.invoke( + package_install_cmd, + ["-e", "devkit", "-p", "platformio/atmelavr@^3", "--skip-dependencies"], + ) + validate_cliresult(result) + assert pkgs_to_specs(PlatformPackageManager().get_installed()) == [ + PackageSpec("atmelavr@3.4.0"), + PackageSpec("atmelavr@2.2.0"), + ] + assert ProjectConfig().get("env:devkit", "platform") == "platformio/atmelavr@^2" + + # unknown platform + result = clirunner.invoke(package_install_cmd, ["-p", "unknown_platform"]) + assert isinstance(result.exception, UnknownPackageError) diff --git a/tests/commands/test_lib.py b/tests/commands/test_lib.py index b2541841..d4faeb1b 100644 --- a/tests/commands/test_lib.py +++ b/tests/commands/test_lib.py @@ -17,6 +17,7 @@ import json import os +import pytest import semantic_version from platformio.clients.registry import RegistryClient @@ -225,5 +226,13 @@ def test_update(clirunner, validate_cliresult, isolated_pio_core, tmpdir_factory result = clirunner.invoke( cmd_lib, ["-d", str(storage_dir), "update", "--dry-run", "ArduinoJson @ ^5"] ) + with pytest.raises( + AssertionError, + match="This command is deprecated", + ): + validate_cliresult(result) + result = clirunner.invoke( + cmd_lib, ["-d", str(storage_dir), "update", "ArduinoJson @ ^5"] + ) validate_cliresult(result) - assert "Incompatible" in result.stdout + assert "ArduinoJson @ 5.13.4 is already up-to-date" in result.stdout diff --git a/tests/commands/test_lib_complex.py b/tests/commands/test_lib_complex.py index 58ec700c..a362db4d 100644 --- a/tests/commands/test_lib_complex.py +++ b/tests/commands/test_lib_complex.py @@ -253,8 +253,8 @@ def test_global_lib_update(clirunner, validate_cliresult, strip_ansi): # 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]") == 13 + assert result.output.count("+sha.") == 4 + assert result.output.count("already up-to-date") == 14 # update unknown library result = clirunner.invoke(cmd_lib, ["-g", "update", "Unknown"]) @@ -290,17 +290,18 @@ def test_global_lib_uninstall( items1 = [d.basename for d in isolated_pio_core.join("lib").listdir()] items2 = [ - "ArduinoJson", - "ArduinoJson@src-69ebddd821f771debe7ee734d3c7fa81", "AsyncMqttClient", - "AsyncTCP", - "ESP32WebServer", - "ESPAsyncTCP", - "NeoPixelBus", - "PJON", - "PJON@src-79de467ebe19de18287becff0a1fb42d", "platformio-libmirror", "PubSubClient", + "ArduinoJson@src-69ebddd821f771debe7ee734d3c7fa81", + "ESPAsyncTCP@1.2.0", + "AsyncTCP", + "ArduinoJson", + "ESPAsyncTCP", + "ESP32WebServer", + "PJON", + "NeoPixelBus", + "PJON@src-79de467ebe19de18287becff0a1fb42d", "SomeLib", ] assert set(items1) == set(items2) diff --git a/tests/commands/test_update.py b/tests/commands/test_update.py index 90cb09c7..6edddfa3 100644 --- a/tests/commands/test_update.py +++ b/tests/commands/test_update.py @@ -19,7 +19,7 @@ from platformio.commands.update import cli as cmd_update def test_update(clirunner, validate_cliresult, isolated_pio_core): matches = ("Platform Manager", "Library Manager") - result = clirunner.invoke(cmd_update, ["--only-check"]) + result = clirunner.invoke(cmd_update) validate_cliresult(result) assert all(m in result.output for m in matches) result = clirunner.invoke(cmd_update)