From bcfb007c907a3b9f1c805ca91d26342a2ec54275 Mon Sep 17 00:00:00 2001 From: Ivan Kravets Date: Sat, 29 Nov 2014 22:55:32 +0200 Subject: [PATCH] Added Migration Manager which simplifies process with upgrading to a major release --- .pylintrc | 2 +- HISTORY.rst | 6 +- platformio/__main__.py | 49 ++++----- platformio/commands/lib.py | 10 +- platformio/commands/list.py | 2 +- platformio/commands/update.py | 2 +- platformio/exception.py | 5 + platformio/libmanager.py | 14 +++ platformio/maintenance.py | 185 ++++++++++++++++++++++++++++++++++ platformio/pkgmanager.py | 18 ++-- 10 files changed, 240 insertions(+), 53 deletions(-) create mode 100644 platformio/maintenance.py diff --git a/.pylintrc b/.pylintrc index 939f8928..5e3b1e69 100644 --- a/.pylintrc +++ b/.pylintrc @@ -38,7 +38,7 @@ load-plugins= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -disable=C0103,C0111,E0611,F0401,I0011,R0801,R0903 +disable=C0103,C0111,E0611,F0401,I0011,R0801,R0903,R0922 [REPORTS] diff --git a/HISTORY.rst b/HISTORY.rst index e3bcb7de..93d0cf90 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,8 @@ Release History 0.9.0 (?) --------- +* Added *Migration Manager* which simplifies process with upgrading to a + major release * Added *Telemetry Service* which should help us make *PlatformIO* better * Implemented *PlatformIO AppState Manager* which allow to have multiple ``.platformio`` states. @@ -59,11 +61,11 @@ Release History * Added auto-conversation from \*.ino to valid \*.cpp for Arduino/Energia frameworks (`issue #7 `_) * Added `Arduino example `_ - with external library (Adafruit CC3000) + with external library (*Adafruit CC3000*) * Implemented `platformio upgrade `_ command and "auto-check" for the latest version (`issue #8 `_) -* Fixed an issue with "auto-reset" for Raspduino board +* Fixed an issue with "auto-reset" for *Raspduino* board * Fixed a bug with nested libs building 0.4.0 (2014-07-31) diff --git a/platformio/__main__.py b/platformio/__main__.py index ab1cceb3..92dba967 100644 --- a/platformio/__main__.py +++ b/platformio/__main__.py @@ -1,21 +1,19 @@ # Copyright (C) Ivan Kravets # See LICENSE for details. -from os import listdir, makedirs -from os.path import getmtime, isdir, isfile, join +from os import listdir +from os.path import join from sys import exit as sys_exit -from time import time from traceback import format_exc -from click import command, MultiCommand, secho, version_option +import click -from platformio import __version__ -from platformio.commands.upgrade import get_latest_version +from platformio import __version__, maintenance from platformio.exception import PlatformioException, UnknownCLICommand -from platformio.util import get_home_dir, get_source_dir +from platformio.util import get_source_dir -class PlatformioCLI(MultiCommand): # pylint: disable=R0904 +class PlatformioCLI(click.MultiCommand): # pylint: disable=R0904 def list_commands(self, ctx): cmds = [] @@ -28,6 +26,7 @@ class PlatformioCLI(MultiCommand): # pylint: disable=R0904 return cmds def get_command(self, ctx, name): + mod = None try: mod = __import__("platformio.commands." + name, None, None, ["cli"]) @@ -36,35 +35,27 @@ class PlatformioCLI(MultiCommand): # pylint: disable=R0904 return mod.cli -@command(cls=PlatformioCLI) -@version_option(__version__, prog_name="PlatformIO") -def cli(): - pass +@click.command(cls=PlatformioCLI) +@click.version_option(__version__, prog_name="PlatformIO") +@click.pass_context +def cli(ctx): + maintenance.on_platformio_start(ctx) -def autocheck_latest_version(): - check_interval = 3600 * 24 * 7 # 1 week - checkfile = join(get_home_dir(), ".pioupgrade") - if isfile(checkfile) and getmtime(checkfile) > (time() - check_interval): - return False - if not isdir(get_home_dir()): - makedirs(get_home_dir()) - with open(checkfile, "w") as f: - f.write(str(time())) - return get_latest_version() != __version__ +@cli.resultcallback() +@click.pass_context +def process_result(ctx, result): + maintenance.on_platformio_end(ctx, result) def main(): try: - if autocheck_latest_version(): - secho("\nThere is a new version of PlatformIO available.\n" - "Please upgrade it via `platformio upgrade` command.\n", - fg="yellow") - - cli() + cli(None) except Exception as e: # pylint: disable=W0703 + maintenance.on_platformio_exception(e) if isinstance(e, PlatformioException): - sys_exit("Error: " + str(e)) + click.echo("Error: " + str(e)) + sys_exit(1) else: print format_exc() diff --git a/platformio/commands/lib.py b/platformio/commands/lib.py index f25db803..c2350bb4 100644 --- a/platformio/commands/lib.py +++ b/platformio/commands/lib.py @@ -191,13 +191,7 @@ def lib_show(libid): @click.pass_context def lib_update(ctx): lm = LibraryManager(get_lib_dir()) - - lib_ids = [str(item['id']) for item in lm.get_installed().values()] - if not lib_ids: - return - - versions = get_api_result("/lib/version/" + str(",".join(lib_ids))) - for id_ in lib_ids: + for id_, latest_version in (lm.get_latest_versions() or {}).items(): info = lm.get_info(int(id_)) click.echo("Updating [ %s ] %s library:" % ( @@ -205,8 +199,6 @@ def lib_update(ctx): click.style(info['name'], fg="cyan"))) current_version = info['version'] - latest_version = versions[id_] - if latest_version is None: click.secho("Unknown library", fg="red") continue diff --git a/platformio/commands/list.py b/platformio/commands/list.py index eba254ce..7b00e226 100644 --- a/platformio/commands/list.py +++ b/platformio/commands/list.py @@ -11,7 +11,7 @@ def cli(): installed_platforms = PlatformFactory.get_platforms( installed=True).keys() - installed_platforms = sorted(installed_platforms) + installed_platforms.sort() for platform in installed_platforms: p = PlatformFactory().newPlatform(platform) diff --git a/platformio/commands/update.py b/platformio/commands/update.py index 285fc45d..2ecd8d3f 100644 --- a/platformio/commands/update.py +++ b/platformio/commands/update.py @@ -11,7 +11,7 @@ def cli(): installed_platforms = PlatformFactory.get_platforms( installed=True).keys() - installed_platforms = sorted(installed_platforms) + installed_platforms.sort() for platform in installed_platforms: echo("\nPlatform %s" % style(platform, fg="cyan")) diff --git a/platformio/exception.py b/platformio/exception.py index 3d8c4494..63cb3aa0 100644 --- a/platformio/exception.py +++ b/platformio/exception.py @@ -139,3 +139,8 @@ class InvalidSettingName(PlatformioException): class InvalidSettingValue(PlatformioException): MESSAGE = "Invalid value '%s' for the setting '%s'" + + +class UpgraderFailed(PlatformioException): + + MESSAGE = "An error occurred while upgrading PlatformIO" diff --git a/platformio/libmanager.py b/platformio/libmanager.py index f85520c3..b669a74b 100644 --- a/platformio/libmanager.py +++ b/platformio/libmanager.py @@ -45,6 +45,20 @@ class LibraryManager(object): items[dirname] = json.load(f) return items + def get_latest_versions(self): + lib_ids = [str(item['id']) for item in self.get_installed().values()] + if not lib_ids: + return None + return get_api_result("/lib/version/" + str(",".join(lib_ids))) + + def get_outdated(self): + outdated = [] + for id_, latest_version in (self.get_latest_versions() or {}).items(): + info = self.get_info(int(id_)) + if latest_version != info['version']: + outdated.append(info['name']) + return outdated + def get_info(self, id_): for item in self.get_installed().values(): if "id" in item and item['id'] == id_: diff --git a/platformio/maintenance.py b/platformio/maintenance.py new file mode 100644 index 00000000..fdfac7f7 --- /dev/null +++ b/platformio/maintenance.py @@ -0,0 +1,185 @@ +# Copyright (C) Ivan Kravets +# See LICENSE for details. + +import re +from os import remove +from os.path import isdir, isfile, join +from shutil import rmtree +from time import time + +import click + +from platformio import __version__, app, telemetry +from platformio.commands.install import cli as cli_install +from platformio.commands.lib import lib_update as cli_libraries_update +from platformio.commands.update import cli as cli_platforms_update +from platformio.commands.upgrade import get_latest_version +from platformio.exception import UpgraderFailed +from platformio.libmanager import LibraryManager +from platformio.platforms.base import PlatformFactory +from platformio.util import get_home_dir, get_lib_dir + + +def on_platformio_start(ctx): + telemetry.on_command(ctx) + after_upgrade(ctx) + check_platformio_upgrade() + check_internal_updates(ctx, "platforms") + check_internal_updates(ctx, "libraries") + + +def on_platformio_end(ctx, result): # pylint: disable=W0613 + pass + + +def on_platformio_exception(e): + telemetry.on_exception(e) + + +class Upgrader(object): + + def __init__(self, from_version, to_version): + self.from_version = self.version_to_int(from_version) + self.to_version = self.version_to_int(to_version) + + @staticmethod + def version_to_int(version): + return int(re.sub(r"[^\d]+", "", version)) + + def run(self, ctx): + if self.from_version > self.to_version: + return True + + result = [True] + for v in (90, ): + if self.from_version >= v: + continue + result.append(getattr(self, "_upgrade_to_%d" % v)(ctx)) + + return all(result) + + def _upgrade_to_90(self, ctx): # pylint: disable=R0201 + prev_platforms = [] + + # remove platform's folder (obsoleted package structure) + for name in PlatformFactory.get_platforms().keys(): + pdir = join(get_home_dir(), name) + if not isdir(pdir): + continue + prev_platforms.append(name) + rmtree(pdir) + + # remove unused files + for fname in (".pioupgrade", "installed.json"): + if isfile(join(get_home_dir(), fname)): + remove(join(get_home_dir(), fname)) + + if prev_platforms: + ctx.invoke(cli_install, platforms=prev_platforms) + + return True + + +def after_upgrade(ctx): + if app.get_state_item("last_version", None) == __version__: + return + + # promotion + click.echo("\nIf you like %s, please:" % ( + click.style("PlatformIO", fg="cyan") + )) + click.echo( + "- %s us on Twitter to stay up-to-date " + "on the latest project news > %s" % + (click.style("follow", fg="cyan"), + click.style("https://twitter.com/platformiotool", fg="blue")) + ) + click.echo("- %s us a star on GitHub > %s" % ( + click.style("give", fg="cyan"), + click.style("https://github.com/ivankravets/platformio", fg="blue") + )) + click.secho("Thanks a lot!\n", fg="green", blink=True) + + if not isdir(get_home_dir()): + return + + click.secho("Please wait while upgrading PlatformIO ...", + fg="yellow") + + last_version = app.get_state_item("last_version", "0.0.0") + u = Upgrader(last_version, __version__) + if u.run(ctx): + app.set_state_item("last_version", __version__) + click.secho("PlatformIO has been successfully upgraded to %s!\n" % + __version__, fg="green") + + telemetry.on_event(category="Auto", action="Upgrade", + label="%s > %s" % (last_version, __version__)) + else: + raise UpgraderFailed() + click.echo("") + + +def check_platformio_upgrade(): + last_check = app.get_state_item("last_check", {}) + interval = int(app.get_setting("check_platformio_interval")) * 3600 * 24 + if (time() - interval) < last_check.get("platformio_upgrade", 0): + return + + last_check['platformio_upgrade'] = int(time()) + app.set_state_item("last_check", last_check) + + latest_version = get_latest_version() + if latest_version == __version__: + return + + click.secho("There is a new version %s of PlatformIO available.\n" + "Please upgrade it via " % latest_version, + fg="yellow", nl=False) + click.secho("`platformio upgrade`", fg="cyan", nl=False) + click.secho(" command.\nChanges: ", fg="yellow", nl=False) + click.secho("http://docs.platformio.ikravets.com/en/latest/history.html\n", + fg="blue") + + +def check_internal_updates(ctx, what): + last_check = app.get_state_item("last_check", {}) + interval = int(app.get_setting("check_%s_interval" % what)) * 3600 * 24 + if (time() - interval) < last_check.get(what + "_update", 0): + return + + last_check[what + '_update'] = int(time()) + app.set_state_item("last_check", last_check) + + outdated_items = [] + if what == "platforms": + for platform in PlatformFactory.get_platforms(installed=True).keys(): + p = PlatformFactory().newPlatform(platform) + if p.is_outdated(): + outdated_items.append(platform) + elif what == "libraries": + lm = LibraryManager(get_lib_dir()) + outdated_items = lm.get_outdated() + + if not outdated_items: + return + + click.secho("There are the new updates for %s (%s)" % + (what, ", ".join(outdated_items)), fg="yellow") + + if not app.get_setting("auto_update_" + what): + click.secho("Please update them via ", fg="yellow", nl=False) + click.secho("`platformio %supdate`" % + ("lib " if what == "libraries" else ""), + fg="cyan", nl=False) + click.secho(" command.\n", fg="yellow") + else: + click.secho("Please wait while updating %s ..." % what, fg="yellow") + if what == "platforms": + ctx.invoke(cli_platforms_update) + elif what == "libraries": + ctx.invoke(cli_libraries_update) + click.echo() + + telemetry.on_event(category="Auto", action="Update", + label=what.title()) diff --git a/platformio/pkgmanager.py b/platformio/pkgmanager.py index bc8be51e..462f02b0 100644 --- a/platformio/pkgmanager.py +++ b/platformio/pkgmanager.py @@ -19,21 +19,19 @@ from platformio.util import get_api_result, get_home_dir, get_systype class PackageManager(object): - DBFILE_PATH = join(get_home_dir(), "installed.json") - def __init__(self): self._package_dir = join(get_home_dir(), "packages") if not isdir(self._package_dir): makedirs(self._package_dir) assert isdir(self._package_dir) - @staticmethod - def get_manifest(): + @classmethod + def get_manifest(cls): try: - return PackageManager._cached_manifest + return cls._cached_manifest except AttributeError: - PackageManager._cached_manifest = get_api_result("/packages") - return PackageManager._cached_manifest + cls._cached_manifest = get_api_result("/packages") + return cls._cached_manifest @staticmethod def download(url, dest_dir, sha1=None): @@ -144,11 +142,11 @@ class PackageManager(object): data = self.get_installed() data[name] = { "version": version, - "time": time() + "time": int(time()) } - self.update_appstate_instpkgs(data) + set_state_item("installed_packages", data) def _unregister(self, name): data = self.get_installed() del data[name] - self.update_appstate_instpkgs(data) + set_state_item("installed_packages", data)