blob: 795f3d4fe13990f15c17b986e7b4a7d5f81982ef [file] [log] [blame]
# Copyright (c) 2024 Basalte bv
#
# SPDX-License-Identifier: Apache-2.0
import argparse
import os
import subprocess
import sys
import textwrap
from itertools import chain
from pathlib import Path
from west.commands import WestCommand
from zephyr_ext_common import ZEPHYR_BASE
sys.path.append(os.fspath(Path(__file__).parent.parent))
import zephyr_module
def in_venv() -> bool:
return sys.prefix != sys.base_prefix
class Packages(WestCommand):
def __init__(self):
super().__init__(
"packages",
"manage packages for Zephyr",
"List and Install packages for Zephyr and modules",
accepts_unknown_args=True,
)
def do_add_parser(self, parser_adder):
parser = parser_adder.add_parser(
self.name,
help=self.help,
description=self.description,
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=textwrap.dedent(
"""
Listing packages:
Run 'west packages <manager>' to list all dependencies
available from a given package manager, already
installed and not. These can be filtered by module,
see 'west packages <manager> --help' for details.
"""
),
)
parser.add_argument(
"-m",
"--module",
action="append",
default=[],
dest="modules",
metavar="<module>",
help="Zephyr module to run the 'packages' command for. "
"Use 'zephyr' if the 'packages' command should run for Zephyr itself. "
"Option can be passed multiple times. "
"If this option is not given, the 'packages' command will run for Zephyr "
"and all modules.",
)
subparsers_gen = parser.add_subparsers(
metavar="<manager>",
dest="manager",
help="select a manager.",
required=True,
)
pip_parser = subparsers_gen.add_parser(
"pip",
help="manage pip packages",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=textwrap.dedent(
"""
Manage pip packages:
Run 'west packages pip' to print all requirement files needed by
Zephyr and modules.
The output is compatible with the requirements file format itself.
"""
),
)
pip_parser.add_argument(
"--install",
action="store_true",
help="Install pip requirements instead of listing them. "
"A single 'pip install' command is built and executed. "
"Additional pip arguments can be passed after a -- separator "
"from the original 'west packages pip --install' command. For example pass "
"'--dry-run' to pip not to actually install anything, but print what would be.",
)
pip_parser.add_argument(
"--ignore-venv-check",
action="store_true",
help="Ignore the virtual environment check. "
"This is useful when running 'west packages pip --install' "
"in a CI environment where the virtual environment is not set up.",
)
return parser
def do_run(self, args, unknown):
if len(unknown) > 0 and unknown[0] != "--":
self.die(
f'Unknown argument "{unknown[0]}"; '
'arguments for the manager should be passed after "--"'
)
# Store the zephyr modules for easier access
self.zephyr_modules = zephyr_module.parse_modules(ZEPHYR_BASE, self.manifest)
if args.modules:
# Check for unknown module names
module_names = [m.meta.get("name") for m in self.zephyr_modules]
module_names.append("zephyr")
for m in args.modules:
if m not in module_names:
self.die(f'Unknown zephyr module "{m}"')
if args.manager == "pip":
return self.do_run_pip(args, unknown[1:])
# Unreachable but print an error message if an implementation is missing.
self.die(f'Unsupported package manager: "{args.manager}"')
def do_run_pip(self, args, manager_args):
requirements = []
if not args.modules or "zephyr" in args.modules:
requirements.append(ZEPHYR_BASE / "scripts/requirements.txt")
for module in self.zephyr_modules:
module_name = module.meta.get("name")
if args.modules and module_name not in args.modules:
if args.install:
self.dbg(f"Skipping module {module_name}")
continue
# Get the optional pip section from the package managers
pip = module.meta.get("package-managers", {}).get("pip")
if pip is None:
if args.install:
self.dbg(f"Nothing to install for {module_name}")
continue
# Add requirements files
requirements += [Path(module.project) / r for r in pip.get("requirement-files", [])]
if args.install:
if not in_venv() and not args.ignore_venv_check:
self.die("Running pip install outside of a virtual environment")
if len(requirements) > 0:
subprocess.check_call(
[sys.executable, "-m", "pip", "install"]
+ list(chain.from_iterable([("-r", r) for r in requirements]))
+ manager_args
)
else:
self.inf("Nothing to install")
return
if len(manager_args) > 0:
self.die(f'west packages pip does not support unknown arguments: "{manager_args}"')
self.inf("\n".join([f"-r {r}" for r in requirements]))