Implement pio pkg list command // Issue #3373

This commit is contained in:
Ivan Kravets
2022-03-24 14:17:18 +02:00
parent 53753c0127
commit 7624645626
4 changed files with 367 additions and 1 deletions

2
docs

Submodule docs updated: cbf179f826...a04cdef33c

View File

@ -16,6 +16,7 @@ import click
from platformio.package.commands.exec import package_exec_cmd from platformio.package.commands.exec import package_exec_cmd
from platformio.package.commands.install import package_install_cmd from platformio.package.commands.install import package_install_cmd
from platformio.package.commands.list import package_list_cmd
from platformio.package.commands.outdated import package_outdated_cmd from platformio.package.commands.outdated import package_outdated_cmd
from platformio.package.commands.pack import package_pack_cmd from platformio.package.commands.pack import package_pack_cmd
from platformio.package.commands.publish import package_publish_cmd from platformio.package.commands.publish import package_publish_cmd
@ -29,6 +30,7 @@ from platformio.package.commands.update import package_update_cmd
commands=[ commands=[
package_exec_cmd, package_exec_cmd,
package_install_cmd, package_install_cmd,
package_list_cmd,
package_outdated_cmd, package_outdated_cmd,
package_pack_cmd, package_pack_cmd,
package_publish_cmd, package_publish_cmd,

View File

@ -0,0 +1,219 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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 os
from typing import List
import click
from platformio import fs
from platformio.package.manager.library import LibraryPackageManager
from platformio.package.manager.platform import PlatformPackageManager
from platformio.package.manager.tool import ToolPackageManager
from platformio.package.meta import PackageItem, PackageSpec
from platformio.platform.factory import PlatformFactory
from platformio.project.config import ProjectConfig
@click.command("list", short_help="List installed packages")
@click.option(
"-d",
"--project-dir",
default=os.getcwd,
type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True),
)
@click.option("-e", "--environment", "environments", multiple=True)
@click.option("-p", "--platform", "platforms", metavar="SPECIFICATION", multiple=True)
@click.option("-t", "--tool", "tools", metavar="SPECIFICATION", multiple=True)
@click.option("-l", "--library", "libraries", metavar="SPECIFICATION", multiple=True)
@click.option("-g", "--global", is_flag=True, help="List globally installed packages")
@click.option(
"--storage-dir",
default=None,
type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True),
help="Custom Package Manager storage for global packages",
)
@click.option("--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-libraries", is_flag=True, help="List only library packages")
@click.option("-v", "--verbose", is_flag=True)
def package_list_cmd(**options):
if options.get("global"):
list_global_packages(options)
else:
list_project_packages(options)
def humanize_package(pkg, spec=None, verbose=False):
if spec and not isinstance(spec, PackageSpec):
spec = PackageSpec(spec)
data = [click.style("{name}@{version}".format(**pkg.metadata.as_dict()), fg="cyan")]
extra_data = ["required: %s" % (spec.humanize() if spec else "Any")]
if verbose:
extra_data.append(pkg.path)
data.append("(%s)" % ", ".join(extra_data))
return " ".join(data)
def print_dependency_tree(pm, specs=None, filter_specs=None, level=0, verbose=False):
filtered_pkgs = [
pm.get_package(spec) for spec in filter_specs or [] if pm.get_package(spec)
]
candidates = {}
if specs:
for spec in specs:
pkg = pm.get_package(spec)
if not pkg:
continue
candidates[pkg.path] = (pkg, spec)
else:
candidates = {pkg.path: (pkg, pkg.metadata.spec) for pkg in pm.get_installed()}
if not candidates:
return
candidates = sorted(candidates.values(), key=lambda item: item[0].metadata.name)
for index, (pkg, spec) in enumerate(candidates):
if filtered_pkgs and not _pkg_tree_contains(pm, pkg, filtered_pkgs):
continue
dependencies = pm.get_pkg_dependencies(pkg)
click.echo(
"%s%s %s"
% (
"" * level,
"├──" if index < len(candidates) - 1 else "└──",
humanize_package(
pkg,
spec=spec,
verbose=verbose,
),
)
)
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]):
if root in children:
return True
for dependency in pm.get_pkg_dependencies(root) or []:
pkg = pm.get_package(pm.dependency_to_spec(dependency))
if pkg and _pkg_tree_contains(pm, pkg, children):
return True
return False
def list_global_packages(options):
data = [
("platforms", PlatformPackageManager(options.get("storage_dir"))),
("tools", ToolPackageManager(options.get("storage_dir"))),
("libraries", LibraryPackageManager(options.get("storage_dir"))),
]
only_packages = any(
options.get(type_) or options.get(f"only_{type_}") for (type_, _) in data
)
for (type_, pm) in data:
skip_conds = [
only_packages
and not options.get(type_)
and not options.get(f"only_{type_}"),
not pm.get_installed(),
]
if any(skip_conds):
continue
click.secho(type_.capitalize(), bold=True)
print_dependency_tree(
pm, filter_specs=options.get(type_), verbose=options.get("verbose")
)
click.echo()
def list_project_packages(options):
environments = options["environments"]
only_packages = any(
options.get(type_) or options.get(f"only_{type_}")
for type_ in ("platforms", "tools", "libraries")
)
only_platform_packages = any(
options.get(type_) or options.get(f"only_{type_}")
for type_ in ("platforms", "tools")
)
only_library_packages = options.get("libraries") or options.get(f"only_libraries")
with fs.cd(options["project_dir"]):
config = ProjectConfig.get_instance()
config.validate(environments)
for env in config.envs():
if environments and env not in environments:
continue
click.echo(
"Resolving %s environment packages..." % click.style(env, fg="cyan")
)
found = False
if not only_packages or only_platform_packages:
_found = print_project_env_platform_packages(env, options)
found = found or _found
if not only_packages or only_library_packages:
_found = print_project_env_library_packages(env, options)
found = found or _found
if not found:
click.echo("No packages")
if (not environments and len(config.envs()) > 1) or len(environments) > 1:
click.echo()
def print_project_env_platform_packages(project_env, options):
config = ProjectConfig.get_instance()
platform = config.get(f"env:{project_env}", "platform")
if not platform:
return None
pkg = PlatformPackageManager().get_package(platform)
if not pkg:
return None
click.echo(
"Platform %s"
% (humanize_package(pkg, platform, verbose=options.get("verbose")))
)
p = PlatformFactory.new(pkg)
if project_env:
p.configure_project_packages(project_env)
print_dependency_tree(
p.pm,
specs=[p.get_package_spec(name) for name in p.packages],
filter_specs=options.get("tools"),
)
click.echo()
return True
def print_project_env_library_packages(project_env, options):
config = ProjectConfig.get_instance()
lib_deps = config.get(f"env:{project_env}", "lib_deps")
lm = LibraryPackageManager(
os.path.join(config.get("platformio", "libdeps_dir"), project_env)
)
if not lib_deps or not lm.get_installed():
return None
click.echo("Libraries")
print_dependency_tree(
lm,
lib_deps,
filter_specs=options.get("libraries"),
verbose=options.get("verbose"),
)
return True

View File

@ -0,0 +1,145 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# pylint: disable=unused-argument
import os
from platformio import fs
from platformio.package.commands.install import package_install_cmd
from platformio.package.commands.list import package_list_cmd
from platformio.package.manager.library import LibraryPackageManager
from platformio.package.manager.platform import PlatformPackageManager
from platformio.package.manager.tool import ToolPackageManager
from platformio.package.meta import PackageSpec
from platformio.project.config import ProjectConfig
PROJECT_CONFIG_TPL = """
[env]
platform = platformio/atmelavr@^3.4.0
[env:baremetal]
board = uno
[env:devkit]
framework = arduino
board = attiny88
lib_deps =
milesburton/DallasTemperature@^3.9.1
https://github.com/bblanchon/ArduinoJson.git#v6.19.0
"""
def test_project(clirunner, validate_cliresult, isolated_pio_core, tmp_path):
project_dir = tmp_path / "project"
project_dir.mkdir()
(project_dir / "platformio.ini").write_text(PROJECT_CONFIG_TPL)
result = clirunner.invoke(
package_install_cmd,
["-d", str(project_dir)],
)
validate_cliresult(result)
# test all envs
result = clirunner.invoke(
package_list_cmd,
["-d", str(project_dir)],
)
validate_cliresult(result)
assert all(token in result.output for token in ("baremetal", "devkit"))
assert result.output.count("Platform atmelavr@3.4.0") == 2
assert (
result.output.count(
"toolchain-atmelavr@1.70300.191015 (required: "
"platformio/toolchain-atmelavr @ ~1.70300.0)"
)
== 2
)
assert result.output.count("Libraries") == 1
assert (
"ArduinoJson@6.19.0+sha.9693fd2 (required: "
"git+https://github.com/bblanchon/ArduinoJson.git#v6.19.0)"
) in result.output
assert "OneWire@2" in result.output
# test "baremetal"
result = clirunner.invoke(
package_list_cmd,
["-d", str(project_dir), "-e", "baremetal"],
)
validate_cliresult(result)
assert "Platform atmelavr@3" in result.output
assert "Libraries" not in result.output
# filter by "tool" package
result = clirunner.invoke(
package_list_cmd,
["-d", str(project_dir), "-t", "toolchain-atmelavr@~1.70300.0"],
)
assert "framework-arduino" not in result.output
assert "Libraries" not in result.output
# list only libraries
result = clirunner.invoke(
package_list_cmd,
["-d", str(project_dir), "--only-libraries"],
)
assert "Platform atmelavr" not in result.output
# list only libraries for baremetal
result = clirunner.invoke(
package_list_cmd,
["-d", str(project_dir), "-e", "baremetal", "--only-libraries"],
)
assert "No packages" in result.output
def test_global_packages(clirunner, validate_cliresult, isolated_pio_core, tmp_path):
result = clirunner.invoke(package_list_cmd, ["-g"])
validate_cliresult(result)
assert "atmelavr@3" in result.output
assert "framework-arduino-avr-attiny" in result.output
# only tools
result = clirunner.invoke(package_list_cmd, ["-g", "--only-tools"])
validate_cliresult(result)
assert "toolchain-atmelavr" in result.output
assert "Platforms" not in result.output
# find tool package
result = clirunner.invoke(package_list_cmd, ["-g", "-t", "toolchain-atmelavr"])
validate_cliresult(result)
assert "toolchain-atmelavr" in result.output
assert "framework-arduino-avr-attiny@" not in result.output
# only libraries - no packages
result = clirunner.invoke(package_list_cmd, ["-g", "--only-libraries"])
validate_cliresult(result)
assert not result.output.strip()
# check global libs
result = clirunner.invoke(
package_install_cmd, ["-g", "-l", "milesburton/DallasTemperature@^3.9.1"]
)
validate_cliresult(result)
result = clirunner.invoke(package_list_cmd, ["-g", "--only-libraries"])
validate_cliresult(result)
assert "DallasTemperature" in result.output
assert "OneWire" in result.output
# filter by lib
result = clirunner.invoke(package_list_cmd, ["-g", "-l", "OneWire"])
validate_cliresult(result)
assert "DallasTemperature" in result.output
assert "OneWire" in result.output