diff --git a/rockcraft/application.py b/rockcraft/application.py index e3dc154e3..46387629b 100644 --- a/rockcraft/application.py +++ b/rockcraft/application.py @@ -34,6 +34,7 @@ ProjectClass=project.Project, BuildPlannerClass=project.BuildPlanner, source_ignore_patterns=["*.rock"], + docs_url="https://documentation.ubuntu.com/rockcraft/en/stable", ) diff --git a/rockcraft/commands/extensions.py b/rockcraft/commands/extensions.py index 9870bde96..aef8946d8 100644 --- a/rockcraft/commands/extensions.py +++ b/rockcraft/commands/extensions.py @@ -97,6 +97,7 @@ class ExpandExtensionsCommand(AppCommand, abc.ABC): @overrides def run(self, parsed_args: argparse.Namespace) -> None: """Print the project's specification with the extensions expanded.""" - project = Project.unmarshal(load_project(Path("rockcraft.yaml"))) + project_path = Path("rockcraft.yaml") + project = Project.from_yaml_data(load_project(project_path), project_path) emit.message(project.to_yaml()) # pylint: disable=no-member diff --git a/rockcraft/models/project.py b/rockcraft/models/project.py index 22c4a0eef..534c566ab 100644 --- a/rockcraft/models/project.py +++ b/rockcraft/models/project.py @@ -263,6 +263,11 @@ def get_build_plan(self) -> list[BuildInfo]: return build_infos + @override + @classmethod + def model_reference_slug(cls) -> str | None: + return "/reference/rockcraft.yaml" + class Project(YamlModelMixin, BuildPlanner, BaseProject): # type: ignore[misc] """Rockcraft project definition.""" @@ -512,6 +517,11 @@ def unmarshal(cls, data: dict[str, Any]) -> Self: return cls(**data) + @override + @classmethod + def model_reference_slug(cls) -> str | None: + return "/reference/rockcraft.yaml" + def load_project(filename: Path) -> dict[str, Any]: """Load and unmarshal the project YAML file. diff --git a/tests/unit/commands/test_expand_extensions.py b/tests/unit/commands/test_expand_extensions.py index 365ee8244..7f097c7df 100644 --- a/tests/unit/commands/test_expand_extensions.py +++ b/tests/unit/commands/test_expand_extensions.py @@ -14,14 +14,22 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . import argparse +import copy +import re import textwrap from pathlib import Path import pytest +from craft_application import util, errors + from rockcraft import extensions from rockcraft.commands import ExpandExtensionsCommand -from tests.unit.testing.extensions import FULL_EXTENSION_YAML, FullExtension +from tests.unit.testing.extensions import ( + FULL_EXTENSION_YAML, + FullExtension, + FULL_EXTENSION_PROJECT, +) # The project with the extension (FullExtension) expanded EXPECTED_EXPAND_EXTENSIONS = textwrap.dedent( @@ -82,3 +90,27 @@ def test_expand_extensions(setup_extensions, emitter, new_dir): cmd.run(argparse.Namespace()) emitter.assert_message(EXPECTED_EXPAND_EXTENSIONS) + + +def test_expand_extensions_error(setup_extensions, new_dir): + wrong_yaml = copy.deepcopy(FULL_EXTENSION_PROJECT) + + # Misconfigure the plugin + wrong_yaml["parts"]["foo"]["plugin"] = "nonexistent" + + # Misconfigure a service + wrong_yaml["services"]["my-service"]["override"] = "invalid" + + project_file = Path("rockcraft.yaml") + dumped = util.dump_yaml(wrong_yaml) + project_file.write_text(dumped) + + expected_message = re.escape( + "Bad rockcraft.yaml content:\n" + "- plugin not registered: 'nonexistent' (in field 'parts.foo')\n" + "- unexpected value; permitted: 'merge', 'replace' (in field 'services.my-service.override')" + ) + + cmd = ExpandExtensionsCommand(None) + with pytest.raises(errors.CraftValidationError, match=expected_message): + cmd.run(argparse.Namespace()) diff --git a/tests/unit/testing/extensions.py b/tests/unit/testing/extensions.py index fed18152b..e6b8d269c 100644 --- a/tests/unit/testing/extensions.py +++ b/tests/unit/testing/extensions.py @@ -15,9 +15,9 @@ # along with this program. If not, see . """Fake Extensions for use in tests.""" -import textwrap from typing import Any +from craft_application import util from overrides import override from rockcraft.extensions.extension import Extension @@ -103,29 +103,23 @@ def get_parts_snippet(self) -> dict[str, Any]: return {"full-extension/new-part": {"plugin": "nil", "source": None}} -FULL_EXTENSION_YAML = textwrap.dedent( - f""" - name: project-with-extensions - version: latest - base: ubuntu@22.04 - summary: Project with extensions - description: Project with extensions - license: Apache-2.0 - platforms: - amd64: - - extensions: - - {FullExtension.NAME} - - parts: - foo: - plugin: nil - stage-packages: - - old-package - - services: - my-service: - command: foo - override: merge - """ -) +FULL_EXTENSION_PROJECT = { + "name": "project-with-extensions", + "version": "latest", + "base": "ubuntu@22.04", + "summary": "Project with extensions", + "description": "Project with extensions", + "license": "Apache-2.0", + "platforms": {"amd64": None}, + "extensions": [FullExtension.NAME], + "parts": {"foo": {"plugin": "nil", "stage-packages": ["old-package"]}}, + "services": { + "my-service": { + "command": "foo", + "override": "merge", + } + }, +} + + +FULL_EXTENSION_YAML = util.dump_yaml(FULL_EXTENSION_PROJECT)