diff --git a/snapcraft/extensions/_utils.py b/snapcraft/extensions/_utils.py index 630e0bc433..3aa885be35 100644 --- a/snapcraft/extensions/_utils.py +++ b/snapcraft/extensions/_utils.py @@ -87,10 +87,13 @@ def _apply_extension( app_definition.get(property_name), property_value ) - # Next, apply the part-specific components - part_extension = extension.get_part_snippet() + # Next, apply the part-specific components, this can be plugin + # aware. parts = yaml_data["parts"] - for _part_name, part_definition in parts.items(): + for _, part_definition in parts.items(): + part_extension = extension.get_part_snippet( + plugin_name=part_definition["plugin"] + ) for property_name, property_value in part_extension.items(): part_definition[property_name] = _apply_extension_property( part_definition.get(property_name), property_value diff --git a/snapcraft/extensions/extension.py b/snapcraft/extensions/extension.py index 64e76a87f2..52cdfd902e 100644 --- a/snapcraft/extensions/extension.py +++ b/snapcraft/extensions/extension.py @@ -70,7 +70,7 @@ def get_app_snippet(self) -> Dict[str, Any]: """Return the app snippet to apply.""" @abc.abstractmethod - def get_part_snippet(self) -> Dict[str, Any]: + def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: """Return the part snippet to apply to existing parts.""" @abc.abstractmethod diff --git a/snapcraft/extensions/gnome.py b/snapcraft/extensions/gnome.py index 50cac6c4bc..a82f48a3e4 100644 --- a/snapcraft/extensions/gnome.py +++ b/snapcraft/extensions/gnome.py @@ -166,7 +166,7 @@ def get_root_snippet(self) -> Dict[str, Any]: } @overrides - def get_part_snippet(self) -> Dict[str, Any]: + def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: sdk_snap = self.gnome_snaps.sdk return { diff --git a/snapcraft/extensions/kde_neon.py b/snapcraft/extensions/kde_neon.py index 7daf72537f..3a7ff8a7c3 100644 --- a/snapcraft/extensions/kde_neon.py +++ b/snapcraft/extensions/kde_neon.py @@ -157,7 +157,7 @@ def get_root_snippet(self) -> Dict[str, Any]: } @overrides - def get_part_snippet(self) -> Dict[str, Any]: + def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: sdk_snap = self.kde_snaps.sdk cmake_args = self.ext_info.cmake_args diff --git a/snapcraft/extensions/ros2_humble.py b/snapcraft/extensions/ros2_humble.py index c667007856..56f33f56dc 100644 --- a/snapcraft/extensions/ros2_humble.py +++ b/snapcraft/extensions/ros2_humble.py @@ -106,7 +106,7 @@ def get_app_snippet(self) -> Dict[str, Any]: } @overrides - def get_part_snippet(self) -> Dict[str, Any]: + def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: return { "build-environment": [ {"ROS_VERSION": self.ROS_VERSION}, diff --git a/snapcraft_legacy/internal/project_loader/_extensions/_extension.py b/snapcraft_legacy/internal/project_loader/_extensions/_extension.py index 9f978e2cad..e94a7d6717 100644 --- a/snapcraft_legacy/internal/project_loader/_extensions/_extension.py +++ b/snapcraft_legacy/internal/project_loader/_extensions/_extension.py @@ -63,6 +63,10 @@ def __init__(self, *, extension_name: str, yaml_data: Dict[str, Any]) -> None: self.part_snippet = dict() # type: Dict[str, Any] self.parts = dict() # type: Dict[str, Any] + def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: + """Return the part snippet to apply to a part.""" + return self.part_snippet + def _sanity_check(self, *, extension_name: str, yaml_data: Dict[str, Any]) -> None: base = yaml_data.get("base") diff --git a/snapcraft_legacy/internal/project_loader/_extensions/_utils.py b/snapcraft_legacy/internal/project_loader/_extensions/_utils.py index 1a8d651d54..5fa688a1cf 100644 --- a/snapcraft_legacy/internal/project_loader/_extensions/_utils.py +++ b/snapcraft_legacy/internal/project_loader/_extensions/_utils.py @@ -163,10 +163,13 @@ def _apply_extension( app_definition.get(property_name), property_value ) - # Next, apply the part-specific components - part_extension = extension.part_snippet + # Next, apply the part-specific components, this can be plugin + # aware. parts = yaml_data["parts"] - for part_name, part_definition in parts.items(): + for _, part_definition in parts.items(): + part_extension = extension.get_part_snippet( + plugin_name=part_definition["plugin"] + ) for property_name, property_value in part_extension.items(): part_definition[property_name] = _apply_extension_property( part_definition.get(property_name), property_value diff --git a/tests/legacy/unit/project_loader/extensions/test_utils.py b/tests/legacy/unit/project_loader/extensions/test_utils.py index 8915780a13..cc785248d8 100644 --- a/tests/legacy/unit/project_loader/extensions/test_utils.py +++ b/tests/legacy/unit/project_loader/extensions/test_utils.py @@ -15,8 +15,9 @@ # along with this program. If not, see . import textwrap -from typing import Tuple +from typing import Any, Dict, Tuple +from overrides import overrides from testtools.matchers import Contains, Equals, Not import snapcraft_legacy.yaml_utils.errors @@ -43,6 +44,7 @@ def setUp(self): self.useFixture(_daemon_extension_fixture()) self.useFixture(_adopt_info_extension_fixture()) self.useFixture(_invalid_extension_fixture()) + self.useFixture(_plugin_aware_part_snippet_extension_fixture()) class BasicExtensionTest(ExtensionTestBase): @@ -332,6 +334,61 @@ def test_merge_environment(self): self.run_test() + def test_plugin_aware_part_snippet1(self): + self.set_attributes( + { + "app_definition": textwrap.dedent( + """\ + command: echo 'hello' + extensions: [pluginextension] + """ + ), + "expected_app_definition": { + "command": "echo 'hello'", + }, + "part_definition": textwrap.dedent( + """\ + plugin: catkin + """ + ), + "expected_part_definition": { + "plugin": "catkin", + "prime": [], + "stage": [], + }, + } + ) + + self.run_test() + + def test_plugin_aware_part_snippet2(self): + self.set_attributes( + { + "app_definition": textwrap.dedent( + """\ + command: echo 'hello' + extensions: [pluginextension] + """ + ), + "expected_app_definition": { + "command": "echo 'hello'", + }, + "part_definition": textwrap.dedent( + """\ + plugin: nil + """ + ), + "expected_part_definition": { + "plugin": "nil", + "build-environment": [{"FOO": "bar"}], + "prime": [], + "stage": [], + }, + } + ) + + self.run_test() + def test_merge_build_environment(self): self.set_attributes( { @@ -831,6 +888,36 @@ def __init__(self, extension_name, yaml_data): return fixture_setup.FakeExtension("buildenvironment2", ExtensionImpl) +def _plugin_aware_part_snippet_extension_fixture(): + class ExtensionImpl(Extension): + @staticmethod + def get_supported_bases() -> Tuple[str, ...]: + return ("core20",) + + @staticmethod + def get_supported_confinement() -> Tuple[str, ...]: + return ("strict",) + + def __init__(self, extension_name, yaml_data): + super().__init__(extension_name=extension_name, yaml_data=yaml_data) + self.root_snippet = {} + self.app_snippet = {} + self.part_snippet = { + "build-environment": [ + {"FOO": "bar"}, + ], + } + self.parts = {} + + @overrides + def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: + if plugin_name == "catkin": + return {} + return self.part_snippet + + return fixture_setup.FakeExtension("pluginextension", ExtensionImpl) + + def _plug_extension_fixture(): class ExtensionImpl(Extension): @staticmethod diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index f4b6404e2d..76dcff35b6 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -86,7 +86,10 @@ def get_root_snippet(self) -> Dict[str, Any]: def get_app_snippet(self) -> Dict[str, Any]: return {"plugs": ["fake-plug"]} - def get_part_snippet(self) -> Dict[str, Any]: + def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: + if plugin_name == "catkin": + return {} + return {"after": ["fake-extension/fake-part"]} def get_parts_snippet(self) -> Dict[str, Any]: @@ -122,7 +125,7 @@ def get_root_snippet(self) -> Dict[str, Any]: def get_app_snippet(self) -> Dict[str, Any]: return {"plugs": ["fake-plug", "fake-plug-extra"]} - def get_part_snippet(self) -> Dict[str, Any]: + def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: return {"after": ["fake-extension-extra/fake-part"]} def get_parts_snippet(self) -> Dict[str, Any]: @@ -156,7 +159,7 @@ def get_root_snippet(self) -> Dict[str, Any]: def get_app_snippet(self) -> Dict[str, Any]: return {"plugs": ["fake-plug"]} - def get_part_snippet(self) -> Dict[str, Any]: + def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: return {"after": ["fake-extension/fake-part"]} def get_parts_snippet(self) -> Dict[str, Any]: @@ -192,7 +195,7 @@ def get_root_snippet(self) -> Dict[str, Any]: def get_app_snippet(self) -> Dict[str, Any]: return {} - def get_part_snippet(self) -> Dict[str, Any]: + def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: return {} def get_parts_snippet(self) -> Dict[str, Any]: @@ -228,7 +231,7 @@ def get_root_snippet(self) -> Dict[str, Any]: def get_app_snippet(self) -> Dict[str, Any]: return {"plugs": ["fake-plug", "fake-plug-extra"]} - def get_part_snippet(self) -> Dict[str, Any]: + def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: return {"after": ["fake-extension-extra/fake-part"]} def get_parts_snippet(self) -> Dict[str, Any]: diff --git a/tests/unit/extensions/test_extensions.py b/tests/unit/extensions/test_extensions.py index 5fb0960080..88547f2159 100644 --- a/tests/unit/extensions/test_extensions.py +++ b/tests/unit/extensions/test_extensions.py @@ -67,6 +67,52 @@ def test_apply_extension(): } +@pytest.mark.usefixtures("fake_extension") +def test_apply_extension_plugin_dependent(): + yaml_data = { + "name": "fake-snap", + "summary": "fake summary", + "description": "fake description", + "base": "core22", + "apps": { + "fake-command": { + "command": "bin/fake-command", + "plugs": ["my-fake-plug"], + "extensions": ["fake-extension"], + } + }, + "parts": { + "fake-part": {"source": ".", "plugin": "dump"}, + "ros-untouched": {"source": "ros", "plugin": "catkin"}, + }, + } + + assert extensions.apply_extensions( + yaml_data, arch="amd64", target_arch="amd64" + ) == { + "name": "fake-snap", + "summary": "fake summary", + "description": "fake description", + "base": "core22", + "grade": "fake-grade", + "apps": { + "fake-command": { + "command": "bin/fake-command", + "plugs": ["fake-plug", "my-fake-plug"], + } + }, + "parts": { + "fake-part": { + "source": ".", + "plugin": "dump", + "after": ["fake-extension/fake-part"], + }, + "ros-untouched": {"source": "ros", "plugin": "catkin"}, + "fake-extension/fake-part": {"plugin": "nil"}, + }, + } + + @pytest.mark.usefixtures("fake_extension") @pytest.mark.usefixtures("fake_extension_extra") def test_apply_multiple_extensions(): diff --git a/tests/unit/extensions/test_gnome.py b/tests/unit/extensions/test_gnome.py index f4447255d7..2e60b75222 100644 --- a/tests/unit/extensions/test_gnome.py +++ b/tests/unit/extensions/test_gnome.py @@ -190,7 +190,7 @@ def test_get_part_snippet_latest_edge( @staticmethod def assert_get_part_snippet(gnome_instance): - assert gnome_instance.get_part_snippet() == { + assert gnome_instance.get_part_snippet(plugin_name="autotools") == { "build-environment": [ {"PATH": "/snap/gnome-42-2204-sdk/current/usr/bin${PATH:+:$PATH}"}, { @@ -254,7 +254,7 @@ def assert_get_part_snippet(gnome_instance): def test_get_part_snippet_with_external_sdk(gnome_extension_with_build_snap): - assert gnome_extension_with_build_snap.get_part_snippet() == { + assert gnome_extension_with_build_snap.get_part_snippet(plugin_name="meson") == { "build-environment": [ {"PATH": "/snap/gnome-44-2204-sdk/current/usr/bin${PATH:+:$PATH}"}, { diff --git a/tests/unit/extensions/test_kde_neon.py b/tests/unit/extensions/test_kde_neon.py index 6af25e556f..7b38bfebb8 100644 --- a/tests/unit/extensions/test_kde_neon.py +++ b/tests/unit/extensions/test_kde_neon.py @@ -168,7 +168,7 @@ def test_get_part_snippet_latest_edge( @staticmethod def assert_get_part_snippet(kde_neon_instance): - assert kde_neon_instance.get_part_snippet() == { + assert kde_neon_instance.get_part_snippet(plugin_name="cmake") == { "build-environment": [ { "PATH": ( @@ -193,7 +193,7 @@ def assert_get_part_snippet(kde_neon_instance): def test_get_part_snippet_with_external_sdk(kde_neon_extension_with_build_snap): - assert kde_neon_extension_with_build_snap.get_part_snippet() == { + assert kde_neon_extension_with_build_snap.get_part_snippet(plugin_name="cmake") == { "build-environment": [ { "PATH": ( diff --git a/tests/unit/parts/extensions/test_ros2_humble.py b/tests/unit/parts/extensions/test_ros2_humble.py index 1b82fe48c9..53ad5b921b 100644 --- a/tests/unit/parts/extensions/test_ros2_humble.py +++ b/tests/unit/parts/extensions/test_ros2_humble.py @@ -122,7 +122,7 @@ def test_get_app_snippet(self, setup_method_fixture): def test_get_part_snippet(self, setup_method_fixture): extension = setup_method_fixture() - assert extension.get_part_snippet() == { + assert extension.get_part_snippet(plugin_name="colcon") == { "build-environment": [{"ROS_VERSION": "2"}, {"ROS_DISTRO": "humble"}] }