“pio lib update” and “pio platform update” in JSON format

This commit is contained in:
Ivan Kravets
2017-01-30 01:04:06 +02:00
parent c1e14b671c
commit 34eab69e85
8 changed files with 231 additions and 140 deletions

2
docs

Submodule docs updated: 24e041602f...d019f41070

View File

@ -14,7 +14,7 @@
import sys import sys
VERSION = (3, 3, "0a7") VERSION = (3, 3, "0a8")
__version__ = ".".join([str(s) for s in VERSION]) __version__ = ".".join([str(s) for s in VERSION])
__title__ = "platformio" __title__ = "platformio"

View File

@ -112,12 +112,35 @@ def lib_uninstall(lm, libraries):
"--only-check", "--only-check",
is_flag=True, is_flag=True,
help="Do not update, only check for new version") help="Do not update, only check for new version")
@click.option("--json-output", is_flag=True)
@click.pass_obj @click.pass_obj
def lib_update(lm, libraries, only_check): def lib_update(lm, libraries, only_check, json_output):
if not libraries: if not libraries:
libraries = [str(m.get("id", m['name'])) for m in lm.get_installed()] libraries = []
for library in libraries: for manifest in lm.get_installed():
lm.update(library, only_check=only_check) pkg_dir = manifest['__pkg_dir']
if "@" in pkg_dir and "@vcs-" not in pkg_dir:
continue
elif "@vcs-" in pkg_dir:
libraries.append("%s=%s" % (manifest['name'], manifest['url']))
else:
libraries.append(str(manifest.get("id", manifest['name'])))
if only_check and json_output:
result = []
for library in libraries:
name, requirements, url = lm.parse_pkg_name(library)
latest = lm.outdated(name, requirements, url)
if latest is False:
continue
manifest = lm.load_manifest(
lm.get_package_dir(name, requirements, url))
manifest['versionLatest'] = latest or "Unknown"
result.append(manifest)
return click.echo(json.dumps(result))
else:
for library in libraries:
lm.update(library, only_check=only_check)
def print_lib_item(item): def print_lib_item(item):

View File

@ -114,15 +114,42 @@ def platform_uninstall(platforms):
"--only-check", "--only-check",
is_flag=True, is_flag=True,
help="Do not update, only check for new version") help="Do not update, only check for new version")
def platform_update(platforms, only_packages, only_check): @click.option("--json-output", is_flag=True)
def platform_update(platforms, only_packages, only_check, json_output):
pm = PlatformManager() pm = PlatformManager()
if not platforms: if not platforms:
platforms = set([m['name'] for m in pm.get_installed()]) platforms = []
for platform in platforms: for manifest in pm.get_installed():
click.echo("Platform %s" % click.style(platform, fg="cyan")) pkg_dir = manifest['__pkg_dir']
click.echo("--------") if "@" in pkg_dir and "@vcs-" not in pkg_dir:
pm.update(platform, only_packages=only_packages, only_check=only_check) continue
click.echo() elif "@vcs-" in pkg_dir:
platforms.append("%s=%s" % (manifest['name'], manifest['url']))
else:
platforms.append(manifest['name'])
if only_check and json_output:
result = []
for platform in platforms:
name, requirements, url = pm.parse_pkg_name(platform)
latest = pm.outdated(name, requirements, url)
if latest is False:
continue
manifest = pm.load_manifest(
pm.get_package_dir(name, requirements, url))
if latest is True:
manifest['versionLatest'] = "Out-of-date"
else:
manifest['versionLatest'] = latest or "Unknown"
result.append(manifest)
return click.echo(json.dumps(result))
else:
for platform in platforms:
click.echo("Platform %s" % click.style(platform, fg="cyan"))
click.echo("--------")
pm.update(
platform, only_packages=only_packages, only_check=only_check)
click.echo()
@cli.command("list", short_help="List installed development platforms") @cli.command("list", short_help="List installed development platforms")

View File

@ -268,7 +268,7 @@ def check_internal_updates(ctx, what):
outdated_items = [] outdated_items = []
for manifest in pm.get_installed(): for manifest in pm.get_installed():
if manifest['name'] not in outdated_items and \ if manifest['name'] not in outdated_items and \
pm.is_outdated(manifest['name']): pm.outdated(manifest['name']):
outdated_items.append(manifest['name']) outdated_items.append(manifest['name'])
if not outdated_items: if not outdated_items:

View File

@ -119,6 +119,47 @@ class PkgInstallerMixin(object):
VCS_MANIFEST_NAME = ".piopkgmanager.json" VCS_MANIFEST_NAME = ".piopkgmanager.json"
FILE_CACHE_VALID = "1m" # 1 month
FILE_CACHE_MAX_SIZE = 1024 * 1024
_INSTALLED_CACHE = {}
def reset_cache(self):
if self.package_dir in PkgInstallerMixin._INSTALLED_CACHE:
del PkgInstallerMixin._INSTALLED_CACHE[self.package_dir]
def download(self, url, dest_dir, sha1=None):
cache_key_fname = app.ContentCache.key_from_args(url, "fname")
cache_key_data = app.ContentCache.key_from_args(url, "data")
if self.FILE_CACHE_VALID:
with app.ContentCache() as cc:
fname = cc.get(cache_key_fname)
cache_path = cc.get_cache_path(cache_key_data)
if fname and isfile(cache_path):
dst_path = join(dest_dir, fname)
shutil.copy(cache_path, dst_path)
return dst_path
fd = FileDownloader(url, dest_dir)
fd.start()
if sha1:
fd.verify(sha1)
dst_path = fd.get_filepath()
if not self.FILE_CACHE_VALID or getsize(
dst_path) > PkgInstallerMixin.FILE_CACHE_MAX_SIZE:
return dst_path
with app.ContentCache() as cc:
cc.set(cache_key_fname, basename(dst_path), self.FILE_CACHE_VALID)
cc.set(cache_key_data, "DUMMY", self.FILE_CACHE_VALID)
shutil.copy(dst_path, cc.get_cache_path(cache_key_data))
return dst_path
@staticmethod
def unpack(source_path, dest_dir):
fu = FileUnpacker(source_path, dest_dir)
return fu.start()
def get_vcs_manifest_path(self, pkg_dir): def get_vcs_manifest_path(self, pkg_dir):
for item in os.listdir(pkg_dir): for item in os.listdir(pkg_dir):
if not isdir(join(pkg_dir, item)): if not isdir(join(pkg_dir, item)):
@ -142,7 +183,7 @@ class PkgInstallerMixin(object):
def manifest_exists(self, pkg_dir): def manifest_exists(self, pkg_dir):
return self.get_manifest_path(pkg_dir) is not None return self.get_manifest_path(pkg_dir) is not None
def load_manifest(self, path): def load_manifest(self, path): # pylint: disable=too-many-branches
assert path assert path
pkg_dir = path pkg_dir = path
if isdir(path): if isdir(path):
@ -155,6 +196,13 @@ class PkgInstallerMixin(object):
if isfile(path) and path.endswith(self.VCS_MANIFEST_NAME): if isfile(path) and path.endswith(self.VCS_MANIFEST_NAME):
pkg_dir = dirname(dirname(path)) pkg_dir = dirname(dirname(path))
# return from cache
if self.package_dir in PkgInstallerMixin._INSTALLED_CACHE:
for manifest in PkgInstallerMixin._INSTALLED_CACHE[self.
package_dir]:
if manifest['__pkg_dir'] == pkg_dir:
return manifest
manifest = {} manifest = {}
if path.endswith(".json"): if path.endswith(".json"):
manifest = util.load_json(path) manifest = util.load_json(path)
@ -174,6 +222,22 @@ class PkgInstallerMixin(object):
manifest['__pkg_dir'] = pkg_dir manifest['__pkg_dir'] = pkg_dir
return manifest return manifest
def get_installed(self):
if self.package_dir in PkgInstallerMixin._INSTALLED_CACHE:
return PkgInstallerMixin._INSTALLED_CACHE[self.package_dir]
items = []
for p in sorted(os.listdir(self.package_dir)):
pkg_dir = join(self.package_dir, p)
if not isdir(pkg_dir):
continue
manifest = self.load_manifest(pkg_dir)
if not manifest:
continue
assert "name" in manifest
items.append(manifest)
PkgInstallerMixin._INSTALLED_CACHE[self.package_dir] = items
return items
def check_pkg_structure(self, pkg_dir): def check_pkg_structure(self, pkg_dir):
if self.manifest_exists(pkg_dir): if self.manifest_exists(pkg_dir):
return pkg_dir return pkg_dir
@ -305,11 +369,6 @@ class PkgInstallerMixin(object):
class BasePkgManager(PkgRepoMixin, PkgInstallerMixin): class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
_INSTALLED_CACHE = {}
FILE_CACHE_VALID = "1m" # 1 month
FILE_CACHE_MAX_SIZE = 1024 * 1024
def __init__(self, package_dir, repositories=None): def __init__(self, package_dir, repositories=None):
self.repositories = repositories self.repositories = repositories
self.package_dir = package_dir self.package_dir = package_dir
@ -321,42 +380,6 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
def manifest_names(self): def manifest_names(self):
raise NotImplementedError() raise NotImplementedError()
def download(self, url, dest_dir, sha1=None):
cache_key_fname = app.ContentCache.key_from_args(url, "fname")
cache_key_data = app.ContentCache.key_from_args(url, "data")
if self.FILE_CACHE_VALID:
with app.ContentCache() as cc:
fname = cc.get(cache_key_fname)
cache_path = cc.get_cache_path(cache_key_data)
if fname and isfile(cache_path):
dst_path = join(dest_dir, fname)
shutil.copy(cache_path, dst_path)
return dst_path
fd = FileDownloader(url, dest_dir)
fd.start()
if sha1:
fd.verify(sha1)
dst_path = fd.get_filepath()
if not self.FILE_CACHE_VALID or getsize(
dst_path) > BasePkgManager.FILE_CACHE_MAX_SIZE:
return dst_path
with app.ContentCache() as cc:
cc.set(cache_key_fname, basename(dst_path), self.FILE_CACHE_VALID)
cc.set(cache_key_data, "DUMMY", self.FILE_CACHE_VALID)
shutil.copy(dst_path, cc.get_cache_path(cache_key_data))
return dst_path
@staticmethod
def unpack(source_path, dest_dir):
fu = FileUnpacker(source_path, dest_dir)
return fu.start()
def reset_cache(self):
if self.package_dir in BasePkgManager._INSTALLED_CACHE:
del BasePkgManager._INSTALLED_CACHE[self.package_dir]
def print_message(self, message, nl=True): def print_message(self, message, nl=True):
click.echo("%s: %s" % (self.__class__.__name__, message), nl=nl) click.echo("%s: %s" % (self.__class__.__name__, message), nl=nl)
@ -415,22 +438,6 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
url = None url = None
return (name or text, requirements, url) return (name or text, requirements, url)
def get_installed(self):
if self.package_dir in BasePkgManager._INSTALLED_CACHE:
return BasePkgManager._INSTALLED_CACHE[self.package_dir]
items = []
for p in sorted(os.listdir(self.package_dir)):
pkg_dir = join(self.package_dir, p)
if not isdir(pkg_dir):
continue
manifest = self.load_manifest(pkg_dir)
if not manifest:
continue
assert "name" in manifest
items.append(manifest)
BasePkgManager._INSTALLED_CACHE[self.package_dir] = items
return items
def get_package(self, name, requirements=None, url=None): def get_package(self, name, requirements=None, url=None):
pkg_id = int(name[3:]) if name.startswith("id=") else 0 pkg_id = int(name[3:]) if name.startswith("id=") else 0
best = None best = None
@ -473,20 +480,38 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
package = self.get_package(name, requirements, url) package = self.get_package(name, requirements, url)
return package.get("__pkg_dir") if package else None return package.get("__pkg_dir") if package else None
def is_outdated(self, name, requirements=None, silent=False): def outdated(self, name, requirements=None, url=None):
package_dir = self.get_package_dir(name, requirements) """
Has 3 different results:
`None` - unknown package, VCS is fixed to commit
`False` - package is up-to-date
`String` - a found latest version
"""
latest = None
package_dir = self.get_package_dir(name, requirements, url)
if not package_dir: if not package_dir:
if silent: return None
return
click.secho(
"%s @ %s is not installed" % (name, requirements or "*"),
fg="yellow")
return
if self.get_vcs_manifest_path(package_dir):
return False
manifest = self.load_manifest(package_dir) manifest = self.load_manifest(package_dir)
latest = self.get_latest_repo_version(name, requirements) if self.get_vcs_manifest_path(package_dir):
return latest and manifest['version'] != latest vcs = VCSClientFactory.newClient(
package_dir, manifest['url'], silent=True)
if not vcs.can_be_updated:
return None
latest = vcs.get_latest_revision()
else:
try:
latest = self.get_latest_repo_version(name, requirements)
except (exception.PlatformioException, ValueError):
return None
if not latest:
return None
up_to_date = False
try:
up_to_date = (semantic_version.Version.coerce(manifest['version'])
>= semantic_version.Version.coerce(latest))
except ValueError:
up_to_date = latest == manifest['version']
return False if up_to_date else latest
def install(self, def install(self,
name, name,
@ -571,7 +596,7 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
requirements=None, requirements=None,
only_check=False): only_check=False):
name, requirements, url = self.parse_pkg_name(name, requirements) name, requirements, url = self.parse_pkg_name(name, requirements)
package_dir = self.get_package_dir(name, None, url) package_dir = self.get_package_dir(name, requirements, url)
if not package_dir: if not package_dir:
click.secho( click.secho(
"%s @ %s is not installed" % (name, requirements or "*"), "%s @ %s is not installed" % (name, requirements or "*"),
@ -587,16 +612,29 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
manifest = self.load_manifest(manifest_path) manifest = self.load_manifest(manifest_path)
click.echo( click.echo(
"%s %s @ %s: \t" % ("Checking" "{} {:<40} @ {:<15}".format(
if only_check else "Updating", click.style( "Checking" if only_check else "Updating",
manifest['name'], fg="cyan"), click.style(
manifest['version']), manifest['name'], fg="cyan"),
manifest['version']),
nl=False) nl=False)
if not util.internet_on():
click.echo("[%s]" % (click.style("Off-line", fg="yellow")))
return
latest = self.outdated(name, requirements, url)
if latest is True:
click.echo("[%s]" % (click.style("Out-of-date", fg="red")))
elif latest:
click.echo("[%s]" % (click.style(latest, fg="red")))
elif latest is False:
click.echo("[%s]" % (click.style("Up-to-date", fg="green")))
else:
click.echo("[%s]" % (click.style("Unknown", fg="yellow")))
if only_check or latest is False or (not is_vcs_pkg and not latest):
return
if is_vcs_pkg: if is_vcs_pkg:
if only_check:
click.echo("[%s]" % (click.style("Skip", fg="yellow")))
return
click.echo("[%s]" % (click.style("VCS", fg="yellow")))
vcs = VCSClientFactory.newClient(package_dir, manifest['url']) vcs = VCSClientFactory.newClient(package_dir, manifest['url'])
if not vcs.can_be_updated: if not vcs.can_be_updated:
click.secho( click.secho(
@ -608,36 +646,10 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
with open(manifest_path, "w") as fp: with open(manifest_path, "w") as fp:
manifest['version'] = vcs.get_current_revision() manifest['version'] = vcs.get_current_revision()
json.dump(manifest, fp) json.dump(manifest, fp)
self.reset_cache()
else: else:
latest_version = None
try:
latest_version = self.get_latest_repo_version(name,
requirements)
except exception.PlatformioException:
pass
if not latest_version:
click.echo("[%s]" % (click.style(
"Off-line" if not util.internet_on() else "Unknown",
fg="yellow")))
return
up_to_date = False
try:
up_to_date = (
semantic_version.Version.coerce(manifest['version']) >=
semantic_version.Version.coerce(latest_version))
except ValueError:
up_to_date = latest_version == manifest['version']
if up_to_date:
click.echo("[%s]" % (click.style("Up-to-date", fg="green")))
return
click.echo("[%s]" % (click.style("Out-of-date", fg="red")))
if only_check:
return
self.uninstall(name, manifest['version'], trigger_event=False) self.uninstall(name, manifest['version'], trigger_event=False)
self.install(name, latest_version, trigger_event=False) self.install(name, latest, trigger_event=False)
telemetry.on_event( telemetry.on_event(
category=self.__class__.__name__, category=self.__class__.__name__,

View File

@ -92,9 +92,10 @@ class PlatformManager(BasePkgManager):
self.cleanup_packages(p.packages.keys()) self.cleanup_packages(p.packages.keys())
return True return True
def is_outdated(self, name, requirements=None, silent=False): def outdated(self, name, requirements=None, url=None):
if BasePkgManager.is_outdated(self, name, requirements, silent): latest = BasePkgManager.outdated(self, name, requirements, url)
return True if latest:
return latest
p = PlatformFactory.newPlatform(name, requirements) p = PlatformFactory.newPlatform(name, requirements)
return p.are_outdated_packages() return p.are_outdated_packages()
@ -213,7 +214,7 @@ class PlatformPackagesMixin(object):
continue continue
elif (name in with_packages or elif (name in with_packages or
not (skip_default_package or opts.get("optional", False))): not (skip_default_package or opts.get("optional", False))):
if self.validate_version_requirements(version): if self.is_valid_requirements(version):
self.pm.install(name, version, silent=silent) self.pm.install(name, version, silent=silent)
else: else:
requirements = None requirements = None
@ -228,7 +229,7 @@ class PlatformPackagesMixin(object):
items = {} items = {}
for name, opts in self.packages.items(): for name, opts in self.packages.items():
version = opts.get("version", "") version = opts.get("version", "")
if self.validate_version_requirements(version): if self.is_valid_requirements(version):
package = self.pm.get_package(name, version) package = self.pm.get_package(name, version)
else: else:
package = self.pm.get_package(*self._parse_pkg_name(name, package = self.pm.get_package(*self._parse_pkg_name(name,
@ -240,7 +241,7 @@ class PlatformPackagesMixin(object):
def update_packages(self, only_check=False): def update_packages(self, only_check=False):
for name in self.get_installed_packages(): for name in self.get_installed_packages():
version = self.packages[name].get("version", "") version = self.packages[name].get("version", "")
if self.validate_version_requirements(version): if self.is_valid_requirements(version):
self.pm.update(name, version, only_check) self.pm.update(name, version, only_check)
else: else:
requirements = None requirements = None
@ -250,17 +251,23 @@ class PlatformPackagesMixin(object):
only_check) only_check)
def are_outdated_packages(self): def are_outdated_packages(self):
for name, opts in self.packages.items(): latest = None
version = opts.get("version", "") for name in self.get_installed_packages():
if not self.validate_version_requirements(version): version = self.packages[name].get("version", "")
continue if self.is_valid_requirements(version):
if self.pm.is_outdated(name, version, silent=True): latest = self.pm.outdated(name, version)
else:
requirements = None
if "@" in version:
version, requirements = version.rsplit("@", 1)
latest = self.pm.outdated(name, requirements, version)
if latest or latest is None:
return True return True
return False return False
def get_package_dir(self, name): def get_package_dir(self, name):
version = self.packages[name].get("version", "") version = self.packages[name].get("version", "")
if self.validate_version_requirements(version): if self.is_valid_requirements(version):
return self.pm.get_package_dir(name, version) return self.pm.get_package_dir(name, version)
else: else:
return self.pm.get_package_dir(*self._parse_pkg_name(name, return self.pm.get_package_dir(*self._parse_pkg_name(name,
@ -268,14 +275,14 @@ class PlatformPackagesMixin(object):
def get_package_version(self, name): def get_package_version(self, name):
version = self.packages[name].get("version", "") version = self.packages[name].get("version", "")
if self.validate_version_requirements(version): if self.is_valid_requirements(version):
package = self.pm.get_package(name, version) package = self.pm.get_package(name, version)
else: else:
package = self.pm.get_package(*self._parse_pkg_name(name, version)) package = self.pm.get_package(*self._parse_pkg_name(name, version))
return package['version'] if package else None return package['version'] if package else None
@staticmethod @staticmethod
def validate_version_requirements(requirements): def is_valid_requirements(requirements):
return requirements and "://" not in requirements return requirements and "://" not in requirements
def _parse_pkg_name(self, name, version): def _parse_pkg_name(self, name, version):

View File

@ -25,7 +25,7 @@ from platformio.exception import PlatformioException
class VCSClientFactory(object): class VCSClientFactory(object):
@staticmethod @staticmethod
def newClient(src_dir, remote_url): def newClient(src_dir, remote_url, silent=False):
result = urlparse(remote_url) result = urlparse(remote_url)
type_ = result.scheme type_ = result.scheme
tag = None tag = None
@ -40,7 +40,7 @@ class VCSClientFactory(object):
raise PlatformioException("VCS: Unknown repository type %s" % raise PlatformioException("VCS: Unknown repository type %s" %
remote_url) remote_url)
obj = getattr(modules[__name__], "%sClient" % type_.title())( obj = getattr(modules[__name__], "%sClient" % type_.title())(
src_dir, remote_url, tag) src_dir, remote_url, tag, silent)
assert isinstance(obj, VCSClientBase) assert isinstance(obj, VCSClientBase)
return obj return obj
@ -49,17 +49,21 @@ class VCSClientBase(object):
command = None command = None
def __init__(self, src_dir, remote_url=None, tag=None): def __init__(self, src_dir, remote_url=None, tag=None, silent=False):
self.src_dir = src_dir self.src_dir = src_dir
self.remote_url = remote_url self.remote_url = remote_url
self.tag = tag self.tag = tag
self.silent = silent
self.check_client() self.check_client()
def check_client(self): def check_client(self):
try: try:
assert self.command assert self.command
assert self.run_cmd(["--version"]) if self.silent:
except (AssertionError, OSError): self.get_cmd_output(["--version"])
else:
assert self.run_cmd(["--version"])
except (AssertionError, OSError, PlatformioException):
raise PlatformioException( raise PlatformioException(
"VCS: `%s` client is not installed in your system" % "VCS: `%s` client is not installed in your system" %
self.command) self.command)
@ -82,6 +86,9 @@ class VCSClientBase(object):
def get_current_revision(self): def get_current_revision(self):
raise NotImplementedError raise NotImplementedError
def get_latest_revision(self):
return None if self.can_be_updated else self.get_current_revision()
def run_cmd(self, args, **kwargs): def run_cmd(self, args, **kwargs):
args = [self.command] + args args = [self.command] + args
if "cwd" not in kwargs: if "cwd" not in kwargs:
@ -141,6 +148,16 @@ class GitClient(VCSClientBase):
def get_current_revision(self): def get_current_revision(self):
return self.get_cmd_output(["rev-parse", "--short", "HEAD"]) return self.get_cmd_output(["rev-parse", "--short", "HEAD"])
def get_latest_revision(self):
if not self.can_be_updated:
return self.get_latest_revision()
result = self.get_cmd_output(["ls-remote"])
for line in result.split("\n"):
line = line.strip()
if "HEAD" in line:
return line.split("HEAD", 1)[0].strip()[:7]
return None
class HgClient(VCSClientBase): class HgClient(VCSClientBase):
@ -160,6 +177,11 @@ class HgClient(VCSClientBase):
def get_current_revision(self): def get_current_revision(self):
return self.get_cmd_output(["identify", "--id"]) return self.get_cmd_output(["identify", "--id"])
def get_latest_revision(self):
if not self.can_be_updated:
return self.get_latest_revision()
return self.get_cmd_output(["identify", "--id", self.remote_url])
class SvnClient(VCSClientBase): class SvnClient(VCSClientBase):