diff --git a/snapcraft/commands/extensions.py b/snapcraft/commands/extensions.py index 95beb98052..3115b4b8c6 100644 --- a/snapcraft/commands/extensions.py +++ b/snapcraft/commands/extensions.py @@ -28,7 +28,7 @@ from pydantic import BaseModel from typing_extensions import override -from snapcraft import errors, extensions, models +from snapcraft import const, errors, extensions, models from snapcraft.parts.yaml_utils import ( apply_yaml, extract_parse_info, @@ -55,23 +55,45 @@ def marshal(self) -> dict[str, str]: class ExtensionsCommand(AppCommand): - """List available extensions for all supported bases.""" + """List available extensions.""" name = "extensions" - help_msg = "List available extensions for all supported bases." + help_msg = "List available extensions, optionally for a given base." overview = textwrap.dedent( """ - List available extensions and their corresponding bases. + List available extensions, optionally for a given base. """ ) + @override + def fill_parser(self, parser: argparse.ArgumentParser) -> None: + """Add arguments specific to the extensions command.""" + parser.add_argument( + "--base", + metavar="base", + type=str, + help="Show extensions for ", + ) + @override def run(self, parsed_args: argparse.Namespace) -> None: + base = getattr(parsed_args, "base", None) + + if base in const.ESM_BASES: + raise errors.MaintenanceBase(base=base) + + if base is not None and base not in const.CURRENT_BASES: + raise errors.SnapcraftError(f"{base} not supported") + extension_presentation: dict[str, ExtensionModel] = {} for extension_name in extensions.registry.get_extension_names(): extension_class = extensions.registry.get_extension_class(extension_name) extension_bases = list(extension_class.get_supported_bases()) + + if base is not None and base not in extension_bases: + continue + extension_presentation[extension_name] = ExtensionModel( name=extension_name, bases=extension_bases ) diff --git a/tests/unit/commands/test_list_extensions.py b/tests/unit/commands/test_list_extensions.py index 64d1d15061..be3f388a0b 100644 --- a/tests/unit/commands/test_list_extensions.py +++ b/tests/unit/commands/test_list_extensions.py @@ -57,6 +57,47 @@ def test_command(emitter, fake_app_config): ) +@pytest.mark.usefixtures("fake_extension") +def test_command_base_option(emitter, fake_app_config): + cmd = snapcraft.commands.ExtensionsCommand(fake_app_config) + + cmd.run(Namespace(base="core22")) + + emitter.assert_message( + dedent( + """\ + Extension name Supported bases + -------------------- ---------------------- + fake-extension core22, core24, core26 + gnome core22, core24 + gpu core22, core24, core26 + kde-neon core22, core24 + kde-neon-6 core22, core24 + kde-neon-qt6 core22, core24 + ros2-humble core22 + ros2-humble-desktop core22 + ros2-humble-ros-base core22 + ros2-humble-ros-core core22""" + ) + ) + + +def test_command_base_option_unsupported(fake_app_config): + cmd = snapcraft.commands.ExtensionsCommand(fake_app_config) + + with pytest.raises(errors.SnapcraftError, match="core99 not supported"): + cmd.run(Namespace(base="core99")) + + +def test_command_base_option_esm_base_error(fake_app_config): + cmd = snapcraft.commands.ExtensionsCommand(fake_app_config) + + expected = re.escape("'core20' is not supported on this version of Snapcraft.") + + with pytest.raises(errors.MaintenanceBase, match=expected): + cmd.run(Namespace(base="core20")) + + def test_list_extensions_error(fake_app_config): """Error on removed 'list-extensions' command.""" cmd = snapcraft.commands.ListExtensionsCommand(fake_app_config)