forked from platformio/platformio-core
PlatformIO Library Manager 3.0 // Resolve #588 , Resolve #414, Resolve #498, Resolve #475, Resolve #410, Resolve #461, Resolve #361, Resolve #507, Resolve #554
This commit is contained in:
1 parent
5b5a63cb5f
commit
758396c9ea
34 files changed
+1185
-700
No files matched your search
@@ -14,7 +14,7 @@
|
||||
|
||||
import sys
|
||||
|
||||
VERSION = (3, 0, "0.dev15")
|
||||
VERSION = (3, 0, "0.dev16")
|
||||
__version__ = ".".join([str(s) for s in VERSION])
|
||||
|
||||
__title__ = "platformio"
|
||||
|
||||
+164
-169
@@ -1,4 +1,4 @@
|
||||
# Copyright 2014-2016 Ivan Kravets <me@ikravets.com>
|
||||
# Copyright 2014-present Ivan Kravets <me@ikravets.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -13,51 +13,136 @@
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
from os.path import join
|
||||
|
||||
import click
|
||||
|
||||
from platformio import app, exception
|
||||
from platformio.libmanager import LibraryManager
|
||||
from platformio import app, exception, util
|
||||
from platformio.managers.lib import LibraryManager
|
||||
from platformio.util import get_api_result
|
||||
|
||||
|
||||
@click.group(short_help="Library Manager")
|
||||
@click.option(
|
||||
"-g",
|
||||
"--global",
|
||||
is_flag=True,
|
||||
help="Manager global PlatformIO"
|
||||
" library storage `%s`" % join(util.get_home_dir(), "lib"))
|
||||
@click.option(
|
||||
"-d",
|
||||
"--storage-dir",
|
||||
default=None,
|
||||
type=click.Path(
|
||||
exists=True,
|
||||
file_okay=False,
|
||||
dir_okay=True,
|
||||
writable=True,
|
||||
resolve_path=True),
|
||||
help="Manage custom library storage")
|
||||
@click.pass_context
|
||||
def cli(ctx, **options):
|
||||
# skip commands that don't need storage folder
|
||||
if ctx.invoked_subcommand in ("search", "register") or \
|
||||
(len(ctx.args) == 2 and ctx.args[1] in ("-h", "--help")):
|
||||
return
|
||||
storage_dir = options['storage_dir']
|
||||
if not storage_dir and options['global']:
|
||||
storage_dir = join(util.get_home_dir(), "lib")
|
||||
|
||||
if not storage_dir and not util.is_platformio_project():
|
||||
raise exception.PlatformioException(
|
||||
"The `%s` is not a PlatformIO project.\nTo manage libraries "
|
||||
"in the global storage `%s`, please use "
|
||||
"`platformio lib --global %s` instead." %
|
||||
(util.get_project_dir(), join(util.get_home_dir(), "lib"),
|
||||
ctx.invoked_subcommand))
|
||||
|
||||
ctx.obj = LibraryManager(storage_dir)
|
||||
click.echo("Library Storage: " + storage_dir)
|
||||
|
||||
|
||||
@cli.command("install", short_help="Install library")
|
||||
@click.argument("libraries", required=False, nargs=-1, metavar="[LIBRARY...]")
|
||||
@click.option(
|
||||
"--save",
|
||||
is_flag=True,
|
||||
help="Save installed libraries into the project's platformio.ini "
|
||||
"library dependencies")
|
||||
@click.option(
|
||||
"-q", "--quiet", is_flag=True, help="Suppress progress reporting")
|
||||
@click.pass_obj
|
||||
def lib_install(lm, libraries, save, quiet): # pylint: disable=unused-argument
|
||||
# @TODO "save" option
|
||||
for library in libraries:
|
||||
lm.install(library, quiet=quiet)
|
||||
|
||||
|
||||
@cli.command("uninstall", short_help="Uninstall libraries")
|
||||
@click.argument("libraries", nargs=-1, metavar="[LIBRARY...]")
|
||||
@click.pass_obj
|
||||
def lib_uninstall(lm, libraries):
|
||||
for library in libraries:
|
||||
lm.uninstall(library)
|
||||
|
||||
|
||||
@cli.command("update", short_help="Update installed libraries")
|
||||
@click.argument("libraries", required=False, nargs=-1, metavar="[LIBRARY...]")
|
||||
@click.option(
|
||||
"-c",
|
||||
"--only-check",
|
||||
is_flag=True,
|
||||
help="Do not update, only check for new version")
|
||||
@click.pass_obj
|
||||
def lib_update(lm, libraries, only_check):
|
||||
if not libraries:
|
||||
libraries = [str(m.get("id", m['name'])) for m in lm.get_installed()]
|
||||
for library in libraries:
|
||||
lm.update(library, only_check=only_check)
|
||||
|
||||
#######
|
||||
|
||||
LIBLIST_TPL = ("[{id:^14}] {name:<25} {compatibility:<30} "
|
||||
"\"{authornames}\": {description}")
|
||||
|
||||
|
||||
def echo_liblist_header():
|
||||
click.echo(LIBLIST_TPL.format(
|
||||
id=click.style("ID", fg="green"),
|
||||
name=click.style("Name", fg="cyan"),
|
||||
compatibility=click.style("Compatibility", fg="yellow"),
|
||||
authornames="Authors",
|
||||
description="Description"
|
||||
))
|
||||
click.echo(
|
||||
LIBLIST_TPL.format(
|
||||
id=click.style(
|
||||
"ID", fg="green"),
|
||||
name=click.style(
|
||||
"Name", fg="cyan"),
|
||||
compatibility=click.style(
|
||||
"Compatibility", fg="yellow"),
|
||||
authornames="Authors",
|
||||
description="Description"))
|
||||
|
||||
terminal_width, _ = click.get_terminal_size()
|
||||
click.echo("-" * terminal_width)
|
||||
|
||||
|
||||
def echo_liblist_item(item):
|
||||
click.echo(LIBLIST_TPL.format(
|
||||
id=click.style(str(item['id']), fg="green"),
|
||||
name=click.style(item['name'], fg="cyan"),
|
||||
compatibility=click.style(
|
||||
", ".join(item['frameworks'] + item['platforms']),
|
||||
fg="yellow"
|
||||
),
|
||||
authornames=", ".join(item['authornames']),
|
||||
description=item['description']
|
||||
))
|
||||
|
||||
|
||||
@click.group(short_help="Library Manager")
|
||||
def cli():
|
||||
pass
|
||||
click.echo(
|
||||
LIBLIST_TPL.format(
|
||||
id=click.style(
|
||||
str(item.get("id", "VCS")), fg="green"),
|
||||
name=click.style(
|
||||
item['name'], fg="cyan"),
|
||||
compatibility=click.style(
|
||||
", ".join(
|
||||
item.get("frameworks", ["-"]) + item.get("platforms", [])),
|
||||
fg="yellow"),
|
||||
authornames=", ".join(item.get("authornames", ["Unknown"])),
|
||||
description=item.get("description", item.get("url", ""))) +
|
||||
(" | @" + click.style(
|
||||
item['version'], fg="yellow") if "version" in item else ""))
|
||||
|
||||
|
||||
@cli.command("search", short_help="Search for library")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
@click.option("--page", type=click.INT, default=1)
|
||||
@click.option("-n", "--name", multiple=True)
|
||||
@click.option("-a", "--author", multiple=True)
|
||||
@click.option("-k", "--keyword", multiple=True)
|
||||
@click.option("-f", "--framework", multiple=True)
|
||||
@@ -73,8 +158,9 @@ def lib_search(query, json_output, page, **filters):
|
||||
for value in values:
|
||||
query.append('%s:"%s"' % (key, value))
|
||||
|
||||
result = get_api_result("/lib/search",
|
||||
dict(query=" ".join(query), page=page))
|
||||
result = get_api_result(
|
||||
"/lib/search", dict(
|
||||
query=" ".join(query), page=page))
|
||||
|
||||
if json_output:
|
||||
click.echo(json.dumps(result))
|
||||
@@ -84,17 +170,22 @@ def lib_search(query, json_output, page, **filters):
|
||||
click.secho(
|
||||
"Nothing has been found by your request\n"
|
||||
"Try a less-specific search or use truncation (or wildcard) "
|
||||
"operator", fg="yellow", nl=False)
|
||||
"operator",
|
||||
fg="yellow",
|
||||
nl=False)
|
||||
click.secho(" *", fg="green")
|
||||
click.secho("For example: DS*, PCA*, DHT* and etc.\n", fg="yellow")
|
||||
click.echo("For more examples and advanced search syntax, "
|
||||
"please use documentation:")
|
||||
click.secho("http://docs.platformio.org"
|
||||
"/en/latest/userguide/lib/cmd_search.html\n", fg="cyan")
|
||||
click.secho(
|
||||
"http://docs.platformio.org"
|
||||
"/en/latest/userguide/lib/cmd_search.html\n",
|
||||
fg="cyan")
|
||||
return
|
||||
|
||||
click.secho("Found %d libraries:\n" % result['total'],
|
||||
fg="green" if result['total'] else "yellow")
|
||||
click.secho(
|
||||
"Found %d libraries:\n" % result['total'],
|
||||
fg="green" if result['total'] else "yellow")
|
||||
|
||||
if result['total']:
|
||||
echo_liblist_header()
|
||||
@@ -111,94 +202,17 @@ def lib_search(query, json_output, page, **filters):
|
||||
click.confirm("Show next libraries?")):
|
||||
result = get_api_result(
|
||||
"/lib/search",
|
||||
dict(query=" ".join(query), page=int(result['page']) + 1)
|
||||
)
|
||||
dict(
|
||||
query=" ".join(query), page=int(result['page']) + 1))
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
@cli.command("install", short_help="Install library")
|
||||
@click.argument("libid", type=click.INT, nargs=-1, metavar="[LIBRARY_ID]")
|
||||
@click.option("-v", "--version")
|
||||
@click.pass_context
|
||||
def lib_install(ctx, libid, version):
|
||||
lm = LibraryManager()
|
||||
for id_ in libid:
|
||||
click.echo(
|
||||
"Installing library [ %s ]:" % click.style(str(id_), fg="green"))
|
||||
try:
|
||||
if not lm.install(id_, version):
|
||||
continue
|
||||
|
||||
info = lm.get_info(id_)
|
||||
click.secho(
|
||||
"The library #%s '%s' has been successfully installed!"
|
||||
% (str(id_), info['name']), fg="green")
|
||||
|
||||
if "dependencies" in info:
|
||||
click.secho("Installing dependencies:", fg="yellow")
|
||||
_dependencies = info['dependencies']
|
||||
if not isinstance(_dependencies, list):
|
||||
_dependencies = [_dependencies]
|
||||
for item in _dependencies:
|
||||
try:
|
||||
lib_install_dependency(ctx, item)
|
||||
except AssertionError:
|
||||
raise exception.LibInstallDependencyError(str(item))
|
||||
|
||||
except exception.LibAlreadyInstalled:
|
||||
click.secho("Already installed", fg="yellow")
|
||||
|
||||
|
||||
def lib_install_dependency(ctx, data):
|
||||
assert isinstance(data, dict)
|
||||
query = []
|
||||
for key in data:
|
||||
if key in ("authors", "frameworks", "platforms", "keywords"):
|
||||
values = data[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], value))
|
||||
elif isinstance(data[key], basestring):
|
||||
query.append('+"%s"' % data[key])
|
||||
|
||||
result = get_api_result("/lib/search", dict(query=" ".join(query)))
|
||||
assert result['total'] > 0
|
||||
|
||||
if result['total'] == 1 or not app.get_setting("enable_prompts"):
|
||||
ctx.invoke(lib_install, libid=[result['items'][0]['id']])
|
||||
else:
|
||||
click.secho(
|
||||
"Conflict: More than one dependent libraries have been found "
|
||||
"by request %s:" % json.dumps(data), fg="red")
|
||||
|
||||
echo_liblist_header()
|
||||
for item in result['items']:
|
||||
echo_liblist_item(item)
|
||||
|
||||
deplib_id = click.prompt(
|
||||
"Please choose one dependent library ID",
|
||||
type=click.Choice([str(i['id']) for i in result['items']]))
|
||||
ctx.invoke(lib_install, libid=[int(deplib_id)])
|
||||
|
||||
|
||||
@cli.command("uninstall", short_help="Uninstall libraries")
|
||||
@click.argument("libid", type=click.INT, nargs=-1)
|
||||
def lib_uninstall(libid):
|
||||
lm = LibraryManager()
|
||||
for id_ in libid:
|
||||
info = lm.get_info(id_)
|
||||
if lm.uninstall(id_):
|
||||
click.secho("The library #%s '%s' has been successfully "
|
||||
"uninstalled!" % (str(id_), info['name']), fg="green")
|
||||
|
||||
|
||||
@cli.command("list", short_help="List installed libraries")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def lib_list(json_output):
|
||||
lm = LibraryManager()
|
||||
items = lm.get_installed().values()
|
||||
@click.pass_obj
|
||||
def lib_list(lm, json_output):
|
||||
items = lm.get_installed()
|
||||
|
||||
if json_output:
|
||||
click.echo(json.dumps(items))
|
||||
@@ -208,21 +222,34 @@ def lib_list(json_output):
|
||||
return
|
||||
|
||||
echo_liblist_header()
|
||||
for item in sorted(items, key=lambda i: i['id']):
|
||||
item['authornames'] = [i['name'] for i in item['authors']]
|
||||
for item in sorted(items, key=lambda i: i['name']):
|
||||
if "authors" in item:
|
||||
item['authornames'] = [i['name'] for i in item['authors']]
|
||||
echo_liblist_item(item)
|
||||
|
||||
|
||||
@cli.command("show", short_help="Show details about installed library")
|
||||
@click.argument("libid", type=click.INT)
|
||||
def lib_show(libid):
|
||||
lm = LibraryManager()
|
||||
info = lm.get_info(libid)
|
||||
click.secho(info['name'], fg="cyan")
|
||||
click.echo("-" * len(info['name']))
|
||||
@click.pass_obj
|
||||
@click.argument("library", metavar="[LIBRARY]")
|
||||
def lib_show(lm, library): # pylint: disable=too-many-branches
|
||||
name, requirements, url = lm.parse_pkg_name(library)
|
||||
installed_dir = lm.get_installed_dir(name, requirements, url)
|
||||
if not installed_dir:
|
||||
click.secho(
|
||||
"%s @ %s is not installed" % (name, requirements or "*"),
|
||||
fg="yellow")
|
||||
return
|
||||
|
||||
manifest = lm.load_manifest(installed_dir)
|
||||
|
||||
click.secho(manifest['name'], fg="cyan")
|
||||
click.echo("=" * len(manifest['name']))
|
||||
if "description" in manifest:
|
||||
click.echo(manifest['description'])
|
||||
click.echo()
|
||||
|
||||
_authors = []
|
||||
for author in info['authors']:
|
||||
for author in manifest.get("authors", []):
|
||||
_data = []
|
||||
for key in ("name", "email", "url", "maintainer"):
|
||||
if not author[key]:
|
||||
@@ -234,61 +261,29 @@ def lib_show(libid):
|
||||
else:
|
||||
_data.append(author[key])
|
||||
_authors.append(" ".join(_data))
|
||||
click.echo("Authors: %s" % ", ".join(_authors))
|
||||
if _authors:
|
||||
click.echo("Authors: %s" % ", ".join(_authors))
|
||||
|
||||
click.echo("Keywords: %s" % ", ".join(info['keywords']))
|
||||
if "frameworks" in info:
|
||||
click.echo("Frameworks: %s" % ", ".join(info['frameworks']))
|
||||
if "platforms" in info:
|
||||
click.echo("Platforms: %s" % ", ".join(info['platforms']))
|
||||
click.echo("Version: %s" % info['version'])
|
||||
click.echo()
|
||||
click.echo(info['description'])
|
||||
click.echo()
|
||||
|
||||
|
||||
@cli.command("update", short_help="Update installed libraries")
|
||||
@click.argument("libid", type=click.INT, nargs=-1, required=False,
|
||||
metavar="[LIBRARY_ID]")
|
||||
@click.pass_context
|
||||
def lib_update(ctx, libid):
|
||||
lm = LibraryManager()
|
||||
for id_, latest_version in (lm.get_latest_versions() or {}).items():
|
||||
if libid and int(id_) not in libid:
|
||||
continue
|
||||
|
||||
info = lm.get_info(int(id_))
|
||||
|
||||
click.echo("Updating [ %s ] %s library:" % (
|
||||
click.style(id_, fg="yellow"),
|
||||
click.style(info['name'], fg="cyan")))
|
||||
|
||||
current_version = info['version']
|
||||
if latest_version is None:
|
||||
click.secho("Unknown library", fg="red")
|
||||
continue
|
||||
|
||||
click.echo("Versions: Current=%s, Latest=%s \t " % (
|
||||
current_version, latest_version), nl=False)
|
||||
|
||||
if current_version == latest_version:
|
||||
click.echo("[%s]" % (click.style("Up-to-date", fg="green")))
|
||||
for key in ("keywords", "frameworks", "platforms", "license", "url",
|
||||
"version"):
|
||||
if key not in manifest:
|
||||
continue
|
||||
if isinstance(manifest[key], list):
|
||||
click.echo("%s: %s" % (key.title(), ", ".join(manifest[key])))
|
||||
else:
|
||||
click.echo("[%s]" % (click.style("Out-of-date", fg="red")))
|
||||
|
||||
ctx.invoke(lib_uninstall, libid=[int(id_)])
|
||||
ctx.invoke(lib_install, libid=[int(id_)])
|
||||
click.echo("%s: %s" % (key.title(), manifest[key]))
|
||||
|
||||
|
||||
@cli.command("register", short_help="Register new library")
|
||||
@click.argument("config_url")
|
||||
def lib_register(config_url):
|
||||
if (not config_url.startswith("http://") and not
|
||||
config_url.startswith("https://")):
|
||||
if (not config_url.startswith("http://") and
|
||||
not config_url.startswith("https://")):
|
||||
raise exception.InvalidLibConfURL(config_url)
|
||||
|
||||
result = get_api_result("/lib/register", data=dict(config_url=config_url))
|
||||
if "message" in result and result['message']:
|
||||
click.secho(result['message'], fg="green" if "successed" in result and
|
||||
result['successed'] else "red")
|
||||
click.secho(
|
||||
result['message'],
|
||||
fg="green"
|
||||
if "successed" in result and result['successed'] else "red")
|
||||
+13
-20
@@ -21,11 +21,11 @@ from time import time
|
||||
|
||||
import click
|
||||
|
||||
from platformio import __version__, app, exception, telemetry, util
|
||||
from platformio import __version__, exception, telemetry, util
|
||||
from platformio.commands.lib import lib_install as cmd_lib_install
|
||||
from platformio.commands.platform import \
|
||||
platform_install as cmd_platform_install
|
||||
from platformio.libmanager import LibraryManager
|
||||
from platformio.managers.lib import LibraryManager
|
||||
from platformio.managers.platform import PlatformFactory
|
||||
|
||||
|
||||
@@ -188,8 +188,8 @@ class EnvironmentProcessor(object):
|
||||
|
||||
# install dependent libraries
|
||||
if "lib_install" in self.options:
|
||||
_autoinstall_libs(self.cmd_ctx, self.options['lib_install'])
|
||||
|
||||
_autoinstall_libs(self.cmd_ctx, self.options['lib_install'],
|
||||
self.verbose)
|
||||
|
||||
try:
|
||||
p = PlatformFactory.newPlatform(self.options['platform'])
|
||||
@@ -201,22 +201,15 @@ class EnvironmentProcessor(object):
|
||||
return p.run(build_vars, build_targets, self.verbose)
|
||||
|
||||
|
||||
def _autoinstall_libs(ctx, libids_list):
|
||||
require_libs = [int(l.strip()) for l in libids_list.split(",")]
|
||||
installed_libs = [
|
||||
l['id'] for l in LibraryManager().get_installed().values()
|
||||
]
|
||||
|
||||
not_intalled_libs = set(require_libs) - set(installed_libs)
|
||||
if not require_libs or not not_intalled_libs:
|
||||
return
|
||||
|
||||
if (not app.get_setting("enable_prompts") or
|
||||
click.confirm(
|
||||
"The libraries with IDs '%s' have not been installed yet. "
|
||||
"Would you like to install them now?" %
|
||||
", ".join([str(i) for i in not_intalled_libs]))):
|
||||
ctx.invoke(cmd_lib_install, libid=not_intalled_libs)
|
||||
def _autoinstall_libs(ctx, libids_list, verbose=False):
|
||||
storage_dir = util.get_projectlibdeps_dir()
|
||||
ctx.obj = LibraryManager(storage_dir)
|
||||
if verbose:
|
||||
click.echo("Library Storage: " + storage_dir)
|
||||
ctx.invoke(
|
||||
cmd_lib_install,
|
||||
libraries=[int(l.strip()) for l in libids_list.split(",")],
|
||||
quiet=not verbose)
|
||||
|
||||
|
||||
def _clean_pioenvs_dir(pioenvs_dir):
|
||||
|
||||
+2
-11
@@ -146,18 +146,9 @@ class APIRequestError(PlatformioException):
|
||||
MESSAGE = "[API] {0}"
|
||||
|
||||
|
||||
class LibAlreadyInstalled(PlatformioException):
|
||||
pass
|
||||
class LibNotFound(PlatformioException):
|
||||
|
||||
|
||||
class LibNotInstalled(PlatformioException):
|
||||
|
||||
MESSAGE = "Library #{0:d} has not been installed yet"
|
||||
|
||||
|
||||
class LibInstallDependencyError(PlatformioException):
|
||||
|
||||
MESSAGE = "Error has been occurred for library dependency '{0}'"
|
||||
MESSAGE = "Library `{0}` has not been found in the registry"
|
||||
|
||||
|
||||
class InvalidLibConfURL(PlatformioException):
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
# Copyright 2014-2016 Ivan Kravets <me@ikravets.com>
|
||||
#
|
||||
# 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 re
|
||||
from os import listdir, makedirs, remove, rename
|
||||
from os.path import isdir, isfile, join
|
||||
from shutil import rmtree
|
||||
from tempfile import gettempdir
|
||||
|
||||
from platformio import telemetry, util
|
||||
from platformio.downloader import FileDownloader
|
||||
from platformio.exception import LibAlreadyInstalled, LibNotInstalled
|
||||
from platformio.unpacker import FileUnpacker
|
||||
|
||||
|
||||
class LibraryManager(object):
|
||||
|
||||
CONFIG_NAME = ".library.json"
|
||||
|
||||
def __init__(self, lib_dir=None):
|
||||
self.lib_dir = lib_dir or join(util.get_home_dir(), "lib")
|
||||
|
||||
@staticmethod
|
||||
def download(url, dest_dir):
|
||||
fd = FileDownloader(url, dest_dir)
|
||||
fd.start()
|
||||
return fd.get_filepath()
|
||||
|
||||
@staticmethod
|
||||
def unpack(pkgpath, dest_dir):
|
||||
fu = FileUnpacker(pkgpath, dest_dir)
|
||||
return fu.start()
|
||||
|
||||
def get_installed(self):
|
||||
items = {}
|
||||
if not isdir(self.lib_dir):
|
||||
return items
|
||||
for dirname in sorted(listdir(self.lib_dir)):
|
||||
conf_path = join(self.lib_dir, dirname, self.CONFIG_NAME)
|
||||
if not isfile(conf_path):
|
||||
continue
|
||||
items[dirname] = util.load_json(conf_path)
|
||||
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 util.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_:
|
||||
return item
|
||||
raise LibNotInstalled(id_)
|
||||
|
||||
def is_installed(self, id_):
|
||||
try:
|
||||
return int(self.get_info(id_)['id']) == id_
|
||||
except LibNotInstalled:
|
||||
return False
|
||||
|
||||
def install(self, id_, version=None):
|
||||
if self.is_installed(id_):
|
||||
raise LibAlreadyInstalled()
|
||||
|
||||
dlinfo = util.get_api_result(
|
||||
"/lib/download/" + str(id_),
|
||||
dict(version=version) if version else None
|
||||
)
|
||||
dlpath = None
|
||||
tmplib_dir = join(self.lib_dir, str(id_))
|
||||
try:
|
||||
dlpath = self.download(dlinfo['url'], gettempdir())
|
||||
if not isdir(tmplib_dir):
|
||||
makedirs(tmplib_dir)
|
||||
self.unpack(dlpath, tmplib_dir)
|
||||
finally:
|
||||
if dlpath:
|
||||
remove(dlpath)
|
||||
|
||||
info = self.get_info(id_)
|
||||
rename(tmplib_dir, join(self.lib_dir, "%s_ID%d" % (
|
||||
re.sub(r"[^\da-zA-Z]+", "_", info['name']), id_)))
|
||||
|
||||
telemetry.on_event(
|
||||
category="LibraryManager", action="Install",
|
||||
label="#%d %s" % (id_, info['name'])
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
def uninstall(self, id_):
|
||||
for libdir, item in self.get_installed().iteritems():
|
||||
if "id" in item and item['id'] == id_:
|
||||
rmtree(join(self.lib_dir, libdir))
|
||||
telemetry.on_event(
|
||||
category="LibraryManager", action="Uninstall",
|
||||
label="#%d %s" % (id_, item['name'])
|
||||
)
|
||||
return True
|
||||
raise LibNotInstalled(id_)
|
||||
+19
-16
@@ -22,12 +22,12 @@ import click
|
||||
import semantic_version
|
||||
|
||||
from platformio import __version__, app, exception, telemetry, util
|
||||
from platformio.commands.lib import lib_update as cmd_libraries_update
|
||||
from platformio.commands.lib import lib_update as cmd_lib_update
|
||||
from platformio.commands.platform import \
|
||||
platform_install as cmd_platform_install
|
||||
from platformio.commands.platform import platform_update as cmd_platform_update
|
||||
from platformio.commands.upgrade import get_latest_version
|
||||
from platformio.libmanager import LibraryManager
|
||||
from platformio.managers.lib import LibraryManager
|
||||
from platformio.managers.platform import PlatformManager
|
||||
|
||||
|
||||
@@ -221,16 +221,12 @@ def check_internal_updates(ctx, what):
|
||||
last_check[what + '_update'] = int(time())
|
||||
app.set_state_item("last_check", last_check)
|
||||
|
||||
pm = PlatformManager() if what == "platforms" else LibraryManager()
|
||||
outdated_items = []
|
||||
if what == "platforms":
|
||||
pm = PlatformManager()
|
||||
for manifest in pm.get_installed():
|
||||
if manifest['name'] not in outdated_items and \
|
||||
pm.is_outdated(manifest['name']):
|
||||
outdated_items.append(manifest['name'])
|
||||
elif what == "libraries":
|
||||
lm = LibraryManager()
|
||||
outdated_items = lm.get_outdated()
|
||||
for manifest in pm.get_installed():
|
||||
if manifest['name'] not in outdated_items and \
|
||||
pm.is_outdated(manifest['name']):
|
||||
outdated_items.append(manifest['name'])
|
||||
|
||||
if not outdated_items:
|
||||
return
|
||||
@@ -245,19 +241,26 @@ def check_internal_updates(ctx, what):
|
||||
if not app.get_setting("auto_update_" + what):
|
||||
click.secho("Please update them via ", fg="yellow", nl=False)
|
||||
click.secho("`platformio %s update`" %
|
||||
("lib" if what == "libraries" else "platform"),
|
||||
("lib --global" if what == "libraries" else "platform"),
|
||||
fg="cyan", nl=False)
|
||||
click.secho(" command.\n", fg="yellow")
|
||||
click.secho("If you want to manually check for the new versions "
|
||||
"without updating, please use ", fg="yellow", nl=False)
|
||||
click.secho("`platformio %s update --only-check`" %
|
||||
("lib --global" if what == "libraries" else "platform"),
|
||||
fg="cyan", nl=False)
|
||||
click.secho(" command.", fg="yellow")
|
||||
else:
|
||||
click.secho("Please wait while updating %s ..." % what, fg="yellow")
|
||||
if what == "platforms":
|
||||
ctx.invoke(cmd_platform_update)
|
||||
ctx.invoke(cmd_platform_update, platforms=outdated_items)
|
||||
elif what == "libraries":
|
||||
ctx.invoke(cmd_libraries_update)
|
||||
ctx.obj = pm
|
||||
ctx.invoke(cmd_lib_update, libraries=outdated_items)
|
||||
click.echo()
|
||||
|
||||
telemetry.on_event(category="Auto", action="Update",
|
||||
label=what.title())
|
||||
telemetry.on_event(
|
||||
category="Auto", action="Update", label=what.title())
|
||||
|
||||
click.echo("*" * terminal_width)
|
||||
click.echo("")
|
||||
@@ -0,0 +1,209 @@
|
||||
# Copyright 2014-present Ivan Kravets <me@ikravets.com>
|
||||
#
|
||||
# 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
|
||||
from os.path import join
|
||||
|
||||
import click
|
||||
import semantic_version
|
||||
|
||||
from platformio import app, commands, exception, util
|
||||
from platformio.managers.package import BasePkgManager
|
||||
|
||||
|
||||
class LibraryManager(BasePkgManager):
|
||||
|
||||
def __init__(self, package_dir=None):
|
||||
if not package_dir:
|
||||
package_dir = join(util.get_home_dir(), "lib")
|
||||
BasePkgManager.__init__(self, package_dir)
|
||||
|
||||
@property
|
||||
def manifest_name(self):
|
||||
return ".library.json"
|
||||
|
||||
@staticmethod
|
||||
def max_satisfying_repo_version(versions, requirements=None):
|
||||
|
||||
def _cmp_dates(datestr1, datestr2):
|
||||
from datetime import datetime
|
||||
assert "T" in datestr1 and "T" in datestr2
|
||||
dateformat = "%Y-%m-%d %H:%M:%S"
|
||||
date1 = datetime.strptime(datestr1[:-1].replace("T", " "),
|
||||
dateformat)
|
||||
date2 = datetime.strptime(datestr2[:-1].replace("T", " "),
|
||||
dateformat)
|
||||
if date1 == date2:
|
||||
return 0
|
||||
return -1 if date1 < date2 else 1
|
||||
|
||||
item = None
|
||||
reqspec = None
|
||||
if requirements:
|
||||
try:
|
||||
reqspec = semantic_version.Spec(requirements)
|
||||
except ValueError:
|
||||
pass
|
||||
for v in versions:
|
||||
specver = None
|
||||
try:
|
||||
specver = semantic_version.Version(v['version'], partial=True)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if reqspec:
|
||||
if not specver or specver not in reqspec:
|
||||
continue
|
||||
if not item or semantic_version.Version(
|
||||
item['version'], partial=True) < specver:
|
||||
item = v
|
||||
elif requirements:
|
||||
if requirements == v['version']:
|
||||
return v
|
||||
else:
|
||||
if not item or _cmp_dates(item['date'], v['date']) == -1:
|
||||
item = v
|
||||
return item
|
||||
|
||||
def get_latest_repo_version(self, name, requirements):
|
||||
item = self.max_satisfying_repo_version(
|
||||
util.get_api_result("/lib/versions/%d" % self._get_pkg_id_by_name(
|
||||
name, requirements)), requirements)
|
||||
return item['version'] if item else None
|
||||
|
||||
def _get_pkg_id_by_name(self, name, requirements, quiet=False):
|
||||
if name.startswith("id="):
|
||||
return int(name[3:])
|
||||
# try to find ID from installed packages
|
||||
installed_dir = self.get_installed_dir(name, requirements)
|
||||
if installed_dir:
|
||||
manifest = self.load_manifest(installed_dir)
|
||||
if "id" in manifest:
|
||||
return int(manifest['id'])
|
||||
return int(self.search_for_library({"name": name}, quiet)['id'])
|
||||
|
||||
def _install_from_piorepo(self, name, requirements):
|
||||
assert name.startswith("id=")
|
||||
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))
|
||||
assert dl_data
|
||||
pkg_dir = None
|
||||
try:
|
||||
pkg_dir = self._install_from_url(
|
||||
name, dl_data['url'].replace("http://", "https://"),
|
||||
requirements)
|
||||
except exception.APIRequestError:
|
||||
pkg_dir = self._install_from_url(name, dl_data['url'],
|
||||
requirements)
|
||||
return pkg_dir
|
||||
|
||||
def install(self, name, requirements=None, quiet=False,
|
||||
trigger_event=True):
|
||||
_name, _requirements, _url = self.parse_pkg_name(name, requirements)
|
||||
if not _url:
|
||||
_name = "id=%d" % self._get_pkg_id_by_name(
|
||||
_name, _requirements, quiet=quiet)
|
||||
already_installed = self.get_installed_dir(_name, _requirements, _url)
|
||||
pkg_dir = BasePkgManager.install(self, _name if not _url else name,
|
||||
_requirements, quiet, trigger_event)
|
||||
|
||||
if already_installed:
|
||||
return
|
||||
|
||||
manifest = self.load_manifest(pkg_dir)
|
||||
if "dependencies" not in manifest:
|
||||
return pkg_dir
|
||||
|
||||
if not quiet:
|
||||
click.secho("Installing dependencies", fg="yellow")
|
||||
|
||||
_dependencies = manifest['dependencies']
|
||||
if not isinstance(_dependencies, list):
|
||||
_dependencies = [_dependencies]
|
||||
for filters in _dependencies:
|
||||
assert "name" in filters
|
||||
if any([s in filters.get("version", "") for s in ("\\", "/")]):
|
||||
self.install("{name}={version}".format(**filters))
|
||||
else:
|
||||
lib_info = self.search_for_library(filters, quiet)
|
||||
if filters.get("version"):
|
||||
self.install(
|
||||
lib_info['id'],
|
||||
requirements=filters.get("version"),
|
||||
quiet=quiet,
|
||||
trigger_event=trigger_event)
|
||||
else:
|
||||
self.install(
|
||||
lib_info['id'],
|
||||
quiet=quiet,
|
||||
trigger_event=trigger_event)
|
||||
return pkg_dir
|
||||
|
||||
@staticmethod
|
||||
def search_for_library( # pylint: disable=too-many-branches
|
||||
filters, quiet=False):
|
||||
assert isinstance(filters, dict)
|
||||
assert "name" in filters
|
||||
if not quiet:
|
||||
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(
|
||||
"/lib/search", dict(query=" ".join(query)))
|
||||
if result['total'] == 1:
|
||||
lib_info = result['items'][0]
|
||||
elif result['total'] > 1:
|
||||
click.secho(
|
||||
"Conflict: More than one library has been found "
|
||||
"by request %s:" % json.dumps(filters),
|
||||
fg="red")
|
||||
commands.lib.echo_liblist_header()
|
||||
for item in result['items']:
|
||||
commands.lib.echo_liblist_item(item)
|
||||
|
||||
if not app.get_setting("enable_prompts"):
|
||||
click.echo("Automatically chose the first available library")
|
||||
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:
|
||||
raise exception.LibNotFound(str(filters))
|
||||
if not quiet:
|
||||
click.echo("Found: %s" % click.style(
|
||||
"http://platformio.org/lib/show/{id}/{name}".format(
|
||||
**lib_info),
|
||||
fg="blue"))
|
||||
return lib_info
|
||||
@@ -312,7 +312,7 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
|
||||
if "=" in text and not text.startswith("id="):
|
||||
name, url = text.split("=", 1)
|
||||
|
||||
# Handle GitHub URL (https://github.com/user/package.git)
|
||||
# Handle GitHub URL (https://github.com/user/package)
|
||||
if url.startswith("https://github.com/") and \
|
||||
not url.endswith((".zip", ".tar.gz")):
|
||||
url = "git+" + url
|
||||
@@ -394,6 +394,20 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
|
||||
return best.get("__pkg_dir")
|
||||
return None
|
||||
|
||||
def is_outdated(self, name, requirements=None):
|
||||
installed_dir = self.get_installed_dir(name, requirements)
|
||||
if not installed_dir:
|
||||
click.secho(
|
||||
"%s @ %s is not installed" % (name, requirements or "*"),
|
||||
fg="yellow")
|
||||
return
|
||||
manifest_path = self.get_manifest_path(installed_dir)
|
||||
if manifest_path.endswith(self.VCS_MANIFEST_NAME):
|
||||
return False
|
||||
manifest = self.load_manifest(installed_dir)
|
||||
return manifest['version'] != self.get_latest_repo_version(
|
||||
name, requirements)
|
||||
|
||||
def install(self, name, requirements=None, quiet=False,
|
||||
trigger_event=True):
|
||||
name, requirements, url = self.parse_pkg_name(name, requirements)
|
||||
|
||||
@@ -71,9 +71,10 @@ class PlatformManager(BasePkgManager):
|
||||
return True
|
||||
|
||||
def is_outdated(self, name, requirements=None):
|
||||
if BasePkgManager.is_outdated(self, name, requirements):
|
||||
return True
|
||||
p = PlatformFactory.newPlatform(name, requirements)
|
||||
return (p.are_outdated_packages() or
|
||||
p.version != self.get_latest_repo_version(name, requirements))
|
||||
return p.are_outdated_packages()
|
||||
|
||||
def cleanup_packages(self, names):
|
||||
self.reset_cache()
|
||||
|
||||
+13
-13
@@ -43,10 +43,10 @@ class VCSClientBase(object):
|
||||
|
||||
command = None
|
||||
|
||||
def __init__(self, src_dir, remote_url=None, branch=None):
|
||||
def __init__(self, src_dir, remote_url=None, tag=None):
|
||||
self.src_dir = src_dir
|
||||
self.remote_url = remote_url
|
||||
self.branch = branch
|
||||
self.tag = tag
|
||||
self.check_client()
|
||||
|
||||
def check_client(self):
|
||||
@@ -71,7 +71,7 @@ class VCSClientBase(object):
|
||||
|
||||
@property
|
||||
def can_be_updated(self):
|
||||
return not self.branch
|
||||
return not self.tag
|
||||
|
||||
def get_current_revision(self):
|
||||
raise NotImplementedError
|
||||
@@ -113,19 +113,19 @@ class GitClient(VCSClientBase):
|
||||
|
||||
@property
|
||||
def can_be_updated(self):
|
||||
return not self.branch or not self.is_commit_id(self.branch)
|
||||
return not self.tag or not self.is_commit_id(self.tag)
|
||||
|
||||
def export(self):
|
||||
is_commit = self.is_commit_id(self.branch)
|
||||
is_commit = self.is_commit_id(self.tag)
|
||||
args = ["clone", "--recursive"]
|
||||
if not self.branch or not is_commit:
|
||||
if not self.tag or not is_commit:
|
||||
args += ["--depth", "1"]
|
||||
if self.branch:
|
||||
args += ["--branch", self.branch]
|
||||
if self.tag:
|
||||
args += ["--branch", self.tag]
|
||||
args += [self.remote_url, self.src_dir]
|
||||
assert self.run_cmd(args)
|
||||
if is_commit:
|
||||
return self.run_cmd(["reset", "--hard", self.branch])
|
||||
return self.run_cmd(["reset", "--hard", self.tag])
|
||||
return True
|
||||
|
||||
def update(self):
|
||||
@@ -142,8 +142,8 @@ class HgClient(VCSClientBase):
|
||||
|
||||
def export(self):
|
||||
args = ["clone"]
|
||||
if self.branch:
|
||||
args.extend(["--updaterev", self.branch])
|
||||
if self.tag:
|
||||
args.extend(["--updaterev", self.tag])
|
||||
args.extend([self.remote_url, self.src_dir])
|
||||
return self.run_cmd(args)
|
||||
|
||||
@@ -161,8 +161,8 @@ class SvnClient(VCSClientBase):
|
||||
|
||||
def export(self):
|
||||
args = ["checkout"]
|
||||
if self.branch:
|
||||
args.extend(["--revision", self.branch])
|
||||
if self.tag:
|
||||
args.extend(["--revision", self.tag])
|
||||
args.extend([self.remote_url, self.src_dir])
|
||||
return self.run_cmd(args)
|
||||
|
||||
|
||||
Reference in new issue
Block a user