mirror of
https://github.com/platformio/platformio-core.git
synced 2025-08-01 02:54:25 +02:00
Added --json-output
support for pkg list
command
This commit is contained in:
@@ -12,8 +12,8 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
from typing import List
|
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
@@ -21,13 +21,13 @@ from platformio import fs
|
|||||||
from platformio.package.manager.library import LibraryPackageManager
|
from platformio.package.manager.library import LibraryPackageManager
|
||||||
from platformio.package.manager.platform import PlatformPackageManager
|
from platformio.package.manager.platform import PlatformPackageManager
|
||||||
from platformio.package.manager.tool import ToolPackageManager
|
from platformio.package.manager.tool import ToolPackageManager
|
||||||
from platformio.package.meta import PackageItem, PackageSpec
|
from platformio.package.meta import PackageInfo, PackageItem, PackageSpec
|
||||||
from platformio.platform.exception import UnknownPlatform
|
from platformio.platform.exception import UnknownPlatform
|
||||||
from platformio.platform.factory import PlatformFactory
|
from platformio.platform.factory import PlatformFactory
|
||||||
from platformio.project.config import ProjectConfig
|
from platformio.project.config import ProjectConfig
|
||||||
|
|
||||||
|
|
||||||
@click.command("list", short_help="List installed packages")
|
@click.command("list", short_help="List project packages")
|
||||||
@click.option(
|
@click.option(
|
||||||
"-d",
|
"-d",
|
||||||
"--project-dir",
|
"--project-dir",
|
||||||
@@ -48,79 +48,116 @@ from platformio.project.config import ProjectConfig
|
|||||||
@click.option("--only-platforms", is_flag=True, help="List only platform packages")
|
@click.option("--only-platforms", is_flag=True, help="List only platform packages")
|
||||||
@click.option("--only-tools", is_flag=True, help="List only tool packages")
|
@click.option("--only-tools", is_flag=True, help="List only tool packages")
|
||||||
@click.option("--only-libraries", is_flag=True, help="List only library packages")
|
@click.option("--only-libraries", is_flag=True, help="List only library packages")
|
||||||
|
@click.option("--json-output", is_flag=True)
|
||||||
@click.option("-v", "--verbose", is_flag=True)
|
@click.option("-v", "--verbose", is_flag=True)
|
||||||
def package_list_cmd(**options):
|
def package_list_cmd(**options):
|
||||||
if options.get("global"):
|
data = (
|
||||||
list_global_packages(options)
|
list_global_packages(options)
|
||||||
|
if options.get("global")
|
||||||
|
else list_project_packages(options)
|
||||||
|
)
|
||||||
|
|
||||||
|
if options.get("json_output"):
|
||||||
|
return click.echo(_dump_to_json(data, options))
|
||||||
|
|
||||||
|
def _print_items(typex, items):
|
||||||
|
click.secho(typex.capitalize(), bold=True)
|
||||||
|
print_dependency_tree(items, verbose=options.get("verbose"))
|
||||||
|
click.echo()
|
||||||
|
|
||||||
|
if options.get("global"):
|
||||||
|
for typex, items in data.items():
|
||||||
|
_print_items(typex, items)
|
||||||
else:
|
else:
|
||||||
list_project_packages(options)
|
for env, env_data in data.items():
|
||||||
|
click.echo("Resolving %s dependencies..." % click.style(env, fg="cyan"))
|
||||||
|
for typex, items in env_data.items():
|
||||||
|
_print_items(typex, items)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def humanize_package(pkg, spec=None, verbose=False):
|
def _dump_to_json(data, options):
|
||||||
if spec and not isinstance(spec, PackageSpec):
|
result = {}
|
||||||
spec = PackageSpec(spec)
|
|
||||||
data = [
|
if options.get("global"):
|
||||||
click.style(pkg.metadata.name, fg="cyan"),
|
for typex, items in data.items():
|
||||||
click.style(f"@ {str(pkg.metadata.version)}", bold=True),
|
result[typex] = [info.as_dict(with_manifest=True) for info in items]
|
||||||
]
|
else:
|
||||||
extra_data = ["required: %s" % (spec.humanize() if spec else "Any")]
|
for env, env_data in data.items():
|
||||||
if verbose:
|
result[env] = {}
|
||||||
extra_data.append(pkg.path)
|
for typex, items in env_data.items():
|
||||||
data.append("(%s)" % ", ".join(extra_data))
|
result[env][typex] = [
|
||||||
return " ".join(data)
|
info.as_dict(with_manifest=True) for info in items
|
||||||
|
]
|
||||||
|
return json.dumps(result)
|
||||||
|
|
||||||
|
|
||||||
def print_dependency_tree(pm, specs=None, filter_specs=None, level=0, verbose=False):
|
def build_package_info(pm, specs=None, filter_specs=None, resolve_dependencies=True):
|
||||||
filtered_pkgs = [
|
filtered_pkgs = [
|
||||||
pm.get_package(spec) for spec in filter_specs or [] if pm.get_package(spec)
|
pm.get_package(spec) for spec in filter_specs if pm.get_package(spec)
|
||||||
]
|
]
|
||||||
candidates = {}
|
candidates = []
|
||||||
if specs:
|
if specs:
|
||||||
for spec in specs:
|
for spec in specs:
|
||||||
pkg = pm.get_package(spec)
|
candidates.append(
|
||||||
if not pkg:
|
PackageInfo(
|
||||||
continue
|
spec if isinstance(spec, PackageSpec) else PackageSpec(spec),
|
||||||
candidates[pkg.path] = (pkg, spec)
|
pm.get_package(spec),
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
candidates = {pkg.path: (pkg, pkg.metadata.spec) for pkg in pm.get_installed()}
|
candidates = [PackageInfo(pkg.metadata.spec, pkg) for pkg in pm.get_installed()]
|
||||||
if not candidates:
|
if not candidates:
|
||||||
return
|
return []
|
||||||
candidates = sorted(candidates.values(), key=lambda item: item[0].metadata.name)
|
|
||||||
|
|
||||||
for index, (pkg, spec) in enumerate(candidates):
|
candidates = sorted(
|
||||||
if filtered_pkgs and not _pkg_tree_contains(pm, pkg, filtered_pkgs):
|
candidates,
|
||||||
continue
|
key=lambda info: info.item.metadata.name if info.item else info.spec.humanize(),
|
||||||
printed_pkgs = pm.memcache_get("__printed_pkgs", [])
|
)
|
||||||
if printed_pkgs and pkg.path in printed_pkgs:
|
|
||||||
continue
|
|
||||||
printed_pkgs.append(pkg.path)
|
|
||||||
pm.memcache_set("__printed_pkgs", printed_pkgs)
|
|
||||||
|
|
||||||
click.echo(
|
result = []
|
||||||
"%s%s %s"
|
for info in candidates:
|
||||||
% (
|
if filter_specs and (
|
||||||
"│ " * level,
|
not info.item or not _pkg_tree_contains(pm, info.item, filtered_pkgs)
|
||||||
"├──" if index < len(candidates) - 1 else "└──",
|
):
|
||||||
humanize_package(
|
continue
|
||||||
pkg,
|
if not info.item:
|
||||||
spec=spec,
|
if not info.spec.external and not info.spec.owner: # built-in library?
|
||||||
verbose=verbose,
|
continue
|
||||||
|
result.append(info)
|
||||||
|
continue
|
||||||
|
|
||||||
|
visited_pkgs = pm.memcache_get("__visited_pkgs", [])
|
||||||
|
if visited_pkgs and info.item.path in visited_pkgs:
|
||||||
|
continue
|
||||||
|
visited_pkgs.append(info.item.path)
|
||||||
|
pm.memcache_set("__visited_pkgs", visited_pkgs)
|
||||||
|
|
||||||
|
result.append(
|
||||||
|
PackageInfo(
|
||||||
|
info.spec,
|
||||||
|
info.item,
|
||||||
|
(
|
||||||
|
build_package_info(
|
||||||
|
pm,
|
||||||
|
specs=[
|
||||||
|
pm.dependency_to_spec(item)
|
||||||
|
for item in pm.get_pkg_dependencies(info.item)
|
||||||
|
],
|
||||||
|
filter_specs=filter_specs,
|
||||||
|
resolve_dependencies=True,
|
||||||
|
)
|
||||||
|
if resolve_dependencies and pm.get_pkg_dependencies(info.item)
|
||||||
|
else []
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
dependencies = pm.get_pkg_dependencies(pkg)
|
return result
|
||||||
if dependencies:
|
|
||||||
print_dependency_tree(
|
|
||||||
pm,
|
|
||||||
specs=[pm.dependency_to_spec(item) for item in dependencies],
|
|
||||||
filter_specs=filter_specs,
|
|
||||||
level=level + 1,
|
|
||||||
verbose=verbose,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _pkg_tree_contains(pm, root: PackageItem, children: List[PackageItem]):
|
def _pkg_tree_contains(pm, root: PackageItem, children: list[PackageItem]):
|
||||||
if root in children:
|
if root in children:
|
||||||
return True
|
return True
|
||||||
for dependency in pm.get_pkg_dependencies(root) or []:
|
for dependency in pm.get_pkg_dependencies(root) or []:
|
||||||
@@ -139,6 +176,7 @@ def list_global_packages(options):
|
|||||||
only_packages = any(
|
only_packages = any(
|
||||||
options.get(typex) or options.get(f"only_{typex}") for (typex, _) in data
|
options.get(typex) or options.get(f"only_{typex}") for (typex, _) in data
|
||||||
)
|
)
|
||||||
|
result = {}
|
||||||
for typex, pm in data:
|
for typex, pm in data:
|
||||||
skip_conds = [
|
skip_conds = [
|
||||||
only_packages
|
only_packages
|
||||||
@@ -148,82 +186,115 @@ def list_global_packages(options):
|
|||||||
]
|
]
|
||||||
if any(skip_conds):
|
if any(skip_conds):
|
||||||
continue
|
continue
|
||||||
click.secho(typex.capitalize(), bold=True)
|
result[typex] = build_package_info(pm, filter_specs=options.get(typex))
|
||||||
print_dependency_tree(
|
|
||||||
pm, filter_specs=options.get(typex), verbose=options.get("verbose")
|
return result
|
||||||
)
|
|
||||||
click.echo()
|
|
||||||
|
|
||||||
|
|
||||||
def list_project_packages(options):
|
def list_project_packages(options):
|
||||||
environments = options["environments"]
|
environments = options["environments"]
|
||||||
only_packages = any(
|
only_filtered_packages = any(
|
||||||
options.get(typex) or options.get(f"only_{typex}")
|
options.get(typex) or options.get(f"only_{typex}")
|
||||||
for typex in ("platforms", "tools", "libraries")
|
for typex in ("platforms", "tools", "libraries")
|
||||||
)
|
)
|
||||||
only_platform_packages = any(
|
only_platform_package = options.get("platforms") or options.get("only_platforms")
|
||||||
options.get(typex) or options.get(f"only_{typex}")
|
only_tool_packages = options.get("tools") or options.get("only_tools")
|
||||||
for typex in ("platforms", "tools")
|
|
||||||
)
|
|
||||||
only_library_packages = options.get("libraries") or options.get("only_libraries")
|
only_library_packages = options.get("libraries") or options.get("only_libraries")
|
||||||
|
|
||||||
|
result = {}
|
||||||
with fs.cd(options["project_dir"]):
|
with fs.cd(options["project_dir"]):
|
||||||
config = ProjectConfig.get_instance()
|
config = ProjectConfig.get_instance()
|
||||||
config.validate(environments)
|
config.validate(environments)
|
||||||
for env in config.envs():
|
for env in config.envs():
|
||||||
if environments and env not in environments:
|
if environments and env not in environments:
|
||||||
continue
|
continue
|
||||||
click.echo("Resolving %s dependencies..." % click.style(env, fg="cyan"))
|
result[env] = {}
|
||||||
found = False
|
if not only_filtered_packages or only_platform_package:
|
||||||
if not only_packages or only_platform_packages:
|
result[env]["platforms"] = list_project_env_platform_package(
|
||||||
_found = print_project_env_platform_packages(env, options)
|
env, options
|
||||||
found = found or _found
|
)
|
||||||
if not only_packages or only_library_packages:
|
if not only_filtered_packages or only_tool_packages:
|
||||||
_found = print_project_env_library_packages(env, options)
|
result[env]["tools"] = list_project_env_tool_packages(env, options)
|
||||||
found = found or _found
|
if not only_filtered_packages or only_library_packages:
|
||||||
if not found:
|
result[env]["libraries"] = list_project_env_library_packages(
|
||||||
click.echo("No packages")
|
env, options
|
||||||
if (not environments and len(config.envs()) > 1) or len(environments) > 1:
|
)
|
||||||
click.echo()
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def print_project_env_platform_packages(project_env, options):
|
def list_project_env_platform_package(project_env, options):
|
||||||
try:
|
pm = PlatformPackageManager()
|
||||||
p = PlatformFactory.from_env(project_env)
|
return build_package_info(
|
||||||
except UnknownPlatform:
|
pm,
|
||||||
return None
|
specs=[PackageSpec(pm.config.get(f"env:{project_env}", "platform"))],
|
||||||
click.echo(
|
filter_specs=options.get("platforms"),
|
||||||
"Platform %s"
|
resolve_dependencies=False,
|
||||||
% (
|
|
||||||
humanize_package(
|
|
||||||
PlatformPackageManager().get_package(p.get_dir()),
|
|
||||||
p.config.get(f"env:{project_env}", "platform"),
|
|
||||||
verbose=options.get("verbose"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
print_dependency_tree(
|
|
||||||
|
|
||||||
|
def list_project_env_tool_packages(project_env, options):
|
||||||
|
try:
|
||||||
|
p = PlatformFactory.from_env(project_env, targets=["upload"])
|
||||||
|
except UnknownPlatform:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return build_package_info(
|
||||||
p.pm,
|
p.pm,
|
||||||
specs=[p.get_package_spec(name) for name in p.packages],
|
specs=[
|
||||||
|
p.get_package_spec(name)
|
||||||
|
for name, options in p.packages.items()
|
||||||
|
if not options.get("optional")
|
||||||
|
],
|
||||||
filter_specs=options.get("tools"),
|
filter_specs=options.get("tools"),
|
||||||
)
|
)
|
||||||
click.echo()
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def print_project_env_library_packages(project_env, options):
|
def list_project_env_library_packages(project_env, options):
|
||||||
config = ProjectConfig.get_instance()
|
config = ProjectConfig.get_instance()
|
||||||
lib_deps = config.get(f"env:{project_env}", "lib_deps")
|
lib_deps = config.get(f"env:{project_env}", "lib_deps")
|
||||||
lm = LibraryPackageManager(
|
lm = LibraryPackageManager(
|
||||||
os.path.join(config.get("platformio", "libdeps_dir"), project_env)
|
os.path.join(config.get("platformio", "libdeps_dir"), project_env)
|
||||||
)
|
)
|
||||||
if not lib_deps or not lm.get_installed():
|
return build_package_info(
|
||||||
return None
|
|
||||||
click.echo("Libraries")
|
|
||||||
print_dependency_tree(
|
|
||||||
lm,
|
lm,
|
||||||
lib_deps,
|
lib_deps,
|
||||||
filter_specs=options.get("libraries"),
|
filter_specs=options.get("libraries"),
|
||||||
verbose=options.get("verbose"),
|
|
||||||
)
|
)
|
||||||
return True
|
|
||||||
|
|
||||||
|
def humanize_package(info, verbose=False):
|
||||||
|
data = (
|
||||||
|
[
|
||||||
|
click.style(info.item.metadata.name, fg="cyan"),
|
||||||
|
click.style(f"@ {str(info.item.metadata.version)}", bold=True),
|
||||||
|
]
|
||||||
|
if info.item
|
||||||
|
else ["Not installed"]
|
||||||
|
)
|
||||||
|
extra_data = ["required: %s" % (info.spec.humanize() if info.spec else "Any")]
|
||||||
|
if verbose and info.item:
|
||||||
|
extra_data.append(info.item.path)
|
||||||
|
data.append("(%s)" % ", ".join(extra_data))
|
||||||
|
return " ".join(data)
|
||||||
|
|
||||||
|
|
||||||
|
def print_dependency_tree(items, verbose=False, level=0):
|
||||||
|
for index, info in enumerate(items):
|
||||||
|
click.echo(
|
||||||
|
"%s%s %s"
|
||||||
|
% (
|
||||||
|
"│ " * level,
|
||||||
|
"├──" if index < len(items) - 1 else "└──",
|
||||||
|
humanize_package(
|
||||||
|
info,
|
||||||
|
verbose=verbose,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if info.dependencies:
|
||||||
|
print_dependency_tree(
|
||||||
|
info.dependencies,
|
||||||
|
verbose=verbose,
|
||||||
|
level=level + 1,
|
||||||
|
)
|
||||||
|
@@ -23,7 +23,7 @@ import semantic_version
|
|||||||
|
|
||||||
from platformio import fs
|
from platformio import fs
|
||||||
from platformio.compat import get_object_members, hashlib_encode_data, string_types
|
from platformio.compat import get_object_members, hashlib_encode_data, string_types
|
||||||
from platformio.package.manifest.parser import ManifestFileType
|
from platformio.package.manifest.parser import ManifestFileType, ManifestParserFactory
|
||||||
from platformio.package.version import SemanticVersionError, cast_version_to_semver
|
from platformio.package.version import SemanticVersionError, cast_version_to_semver
|
||||||
from platformio.util import items_in_list
|
from platformio.util import items_in_list
|
||||||
|
|
||||||
@@ -561,3 +561,29 @@ class PackageItem:
|
|||||||
break
|
break
|
||||||
assert location
|
assert location
|
||||||
return self.metadata.dump(os.path.join(location, self.METAFILE_NAME))
|
return self.metadata.dump(os.path.join(location, self.METAFILE_NAME))
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {"path": self.path, "metadata": self.metadata.as_dict()}
|
||||||
|
|
||||||
|
|
||||||
|
class PackageInfo:
|
||||||
|
|
||||||
|
def __init__(self, spec: PackageSpec, item: PackageItem = None, dependencies=None):
|
||||||
|
assert isinstance(spec, PackageSpec)
|
||||||
|
self.spec = spec
|
||||||
|
self.item = item
|
||||||
|
self.dependencies = dependencies or []
|
||||||
|
|
||||||
|
def as_dict(self, with_manifest=False):
|
||||||
|
result = {
|
||||||
|
"spec": self.spec.as_dict(),
|
||||||
|
"item": self.item.as_dict() if self.item else None,
|
||||||
|
"dependencies": [d.as_dict() for d in self.dependencies],
|
||||||
|
}
|
||||||
|
if with_manifest:
|
||||||
|
result["manifest"] = (
|
||||||
|
ManifestParserFactory.new_from_dir(self.item.path).as_dict()
|
||||||
|
if self.item
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
Reference in New Issue
Block a user