Coverage for src/mafw/devtools/cli/toolchain/bootstrap_cmd.py: 100%
35 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-26 09:13 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-26 09:13 +0000
1# Copyright 2026 European Union
2# Author: Bulgheroni Antonio (antonio.bulgheroni@ec.europa.eu)
3# SPDX-License-Identifier: EUPL-1.2
4"""
5Bootstrap subcommand for the toolchain command group.
7This module implements the ``devtools toolchain bootstrap`` command, which
8iterates over all applicable :class:`~mafw.devtools.toolchain.base.ToolChainTool`
9instances and attempts to install any that are not yet present. Each tool's
10:meth:`~mafw.devtools.toolchain.base.ToolChainTool.bootstrap` method is called;
11the result is categorised as successfully installed, already present, or failed.
13A Rich spinner is displayed while each tool is being processed, providing
14visual feedback during potentially slow installation operations.
16Project tools raise :class:`~mafw.devtools.DevtoolsError` from ``bootstrap()``
17to indicate they do not support standalone bootstrapping (they are managed
18through pyproject.toml and installed automatically via Hatch environments).
19This is treated as a skip and does not affect the exit code.
20"""
22from __future__ import annotations
24import sys
26import click
27from rich.console import Console
28from rich.panel import Panel
29from rich.status import Status
31from mafw.devtools import DevtoolsError
32from mafw.devtools.cli.toolchain._group import common_options, toolchain
33from mafw.devtools.toolchain.filtering import resolve_tools
36@toolchain.command(name='bootstrap')
37@common_options
38@click.pass_context
39def bootstrap(ctx: click.Context, include: tuple[str, ...], exclude: tuple[str, ...]) -> None:
40 """Install missing tools to set up the development environment.
42 Iterates over the filtered tool set in registration order, calling each
43 tool's ``bootstrap()`` method. A Rich spinner provides visual feedback
44 while each tool is being processed. Results are reported per-tool:
46 - Successfully installed tools get a green confirmation message.
47 - Tools already present (indicated by a :class:`~mafw.devtools.DevtoolsError`
48 mentioning project-tool status) get a dimmed skip message.
49 - Real installation failures are displayed in a Rich Panel and count
50 towards a non-zero exit code.
51 """
52 registry = ctx.obj['registry']
53 include_host: bool = ctx.obj['include_host']
54 console = Console()
56 tools = resolve_tools(registry, include=include, exclude=exclude, include_host=include_host)
58 has_failure = False
60 for tool in tools:
61 # Display a spinner while processing each tool (requirement 8.7).
62 with Status(f'Bootstrapping [bold]{tool.name}[/bold]…', console=console):
63 try:
64 installed = tool.bootstrap()
65 except DevtoolsError as exc:
66 # Project tools raise DevtoolsError to indicate they don't support
67 # standalone bootstrapping — treat this as a skip, not a failure.
68 if tool.category == 'project':
69 console.print(f'[dim]—[/dim] [bold]{tool.name}[/bold]: skipped (project tool)')
70 else:
71 # Host tools raising DevtoolsError indicates a real failure
72 # (e.g. pipx not available, install command failed).
73 has_failure = True
74 console.print(
75 Panel(
76 f'[bold]{tool.name}[/bold]: {exc}',
77 title='Bootstrap Error',
78 border_style='red',
79 )
80 )
81 except Exception as exc: # noqa: BLE001
82 # Unexpected errors are always treated as failures.
83 has_failure = True
84 console.print(
85 Panel(
86 f'[bold]{tool.name}[/bold]: {exc}',
87 title='Bootstrap Error',
88 border_style='red',
89 )
90 )
91 else:
92 if installed:
93 console.print(f'[green]✓[/green] [bold]{tool.name}[/bold] installed successfully')
94 else:
95 # bootstrap() returned False — tool was already present.
96 console.print(f'[dim]—[/dim] [bold]{tool.name}[/bold] already installed')
98 # Exit with non-zero code if any tool failed to install.
99 if has_failure:
100 sys.exit(1)