Skip to content

Commit

Permalink
Merge branch 'feature/craft-application' into work/CRAFT-2469-support…
Browse files Browse the repository at this point in the history
…-adopt-info-and-dynamic-versions
  • Loading branch information
cmatsuoka authored Feb 24, 2024
2 parents 0162fac + 72ddcc2 commit 623bb03
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 30 deletions.
30 changes: 22 additions & 8 deletions snapcraft/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@
from craft_providers import bases
from overrides import override

import snapcraft.commands
from snapcraft import cli, errors, models, services
from snapcraft import cli, commands, errors, models, services
from snapcraft.commands import unimplemented
from snapcraft.extensions import apply_extensions
from snapcraft.models import Architecture
from snapcraft.models.project import validate_architectures
from snapcraft.providers import SNAPCRAFT_BASE_TO_PROVIDER_BASE
Expand Down Expand Up @@ -155,6 +155,14 @@ def app_config(self) -> dict[str, Any]:
config["core24"] = self._known_core24
return config

@override
def _extra_yaml_transform(
self, yaml_data: dict[str, Any], *, build_on: str, build_for: str | None
) -> dict[str, Any]:
arch = build_on
target_arch = build_for if build_for else get_host_architecture()
return apply_extensions(yaml_data, arch=arch, target_arch=target_arch)

@override
def _get_dispatcher(self) -> craft_cli.Dispatcher:
"""Configure this application. Should be called by the run method.
Expand Down Expand Up @@ -237,8 +245,8 @@ def _get_dispatcher(self) -> craft_cli.Dispatcher:
return dispatcher


def main() -> int:
"""Run craft-application based snapcraft with classic fallback."""
def create_app() -> Snapcraft:
"""Create a Snapcraft application with the proper commands."""
snapcraft_services = services.SnapcraftServiceFactory(app=APP_METADATA)

app = Snapcraft(
Expand All @@ -256,7 +264,7 @@ def main() -> int:
craft_app_commands.lifecycle.StageCommand,
craft_app_commands.lifecycle.PrimeCommand,
craft_app_commands.lifecycle.PackCommand,
snapcraft.commands.lifecycle.SnapCommand, # Hidden (legacy compatibility)
commands.SnapCommand, # Hidden (legacy compatibility)
unimplemented.RemoteBuild,
unimplemented.Plugins,
unimplemented.ListPlugins,
Expand All @@ -266,9 +274,8 @@ def main() -> int:
app.add_command_group(
"Extensions",
[
unimplemented.ListExtensions,
unimplemented.Extensions,
unimplemented.ExpandExtensions,
commands.ListExtensions,
commands.ExpandExtensions,
],
)
app.add_command_group(
Expand Down Expand Up @@ -339,6 +346,13 @@ def main() -> int:
],
)

return app


def main() -> int:
"""Run craft-application based snapcraft with classic fallback."""
app = create_app()

try:
return app.run()
except errors.ClassicFallback:
Expand Down
3 changes: 3 additions & 0 deletions snapcraft/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
"""Snapcraft commands."""

from . import core22, legacy
from .extensions import ExpandExtensions, ListExtensions
from .lifecycle import SnapCommand

__all__ = [
"core22",
"legacy",
"SnapCommand",
"ExpandExtensions",
"ListExtensions",
]
29 changes: 29 additions & 0 deletions snapcraft/commands/extensions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2024 Canonical Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""Extension commands for core24 (forwarded to the shared core22 implementation)."""

from craft_application.commands import AppCommand

from snapcraft.commands import core22


class ExpandExtensions(AppCommand, core22.ExpandExtensionsCommand):
"""core24 command to expand extensions."""


class ListExtensions(AppCommand, core22.ListExtensionsCommand):
"""core24 command to list extensions."""
6 changes: 3 additions & 3 deletions snapcraft/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,14 +571,14 @@ def _validate_grade_and_build_base(cls, values):
raise ValueError("grade must be 'devel' when build-base is 'devel'")
return values

@pydantic.validator("base", always=True)
@pydantic.root_validator()
@classmethod
def _validate_base(cls, base, values):
def _validate_base(cls, values):
"""Not allowed to use unstable base without devel build-base."""
if values.get("base") == "core24" and values.get("build_base") != "devel":
raise ValueError("build-base must be 'devel' when base is 'core24'")

return base
return values

@pydantic.validator("build_base", always=True)
@classmethod
Expand Down
64 changes: 47 additions & 17 deletions tests/unit/commands/test_expand_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,55 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from argparse import Namespace
from dataclasses import dataclass
from pathlib import Path
from textwrap import dedent

import pytest

import snapcraft.commands.core22
from snapcraft import commands


@dataclass
class CoreData:
"""Dataclass containing base info for a given core."""

base: str
build_base: str
grade: str
command_class: type


VALID_CORE_DATA = {
"core22": CoreData(
"core22", "core22", "stable", commands.core22.ExpandExtensionsCommand
),
"core24": CoreData("core24", "devel", "devel", commands.ExpandExtensions),
}


@pytest.fixture(params=VALID_CORE_DATA.keys())
def valid_core_data(request) -> CoreData:
"""Fixture that provides valid base, build-base and grade values for each
coreXX base."""
return VALID_CORE_DATA[request.param]


@pytest.mark.usefixtures("fake_extension")
def test_expand_extensions_simple(new_dir, emitter):
def test_expand_extensions_simple(new_dir, emitter, valid_core_data):
"""Expand an extension for a simple snapcraft.yaml file."""
with Path("snapcraft.yaml").open("w") as yaml_file:
print(
dedent(
"""\
f"""\
name: test-name
version: "0.1"
summary: testing extensions
description: expand a fake extension
base: core22
base: {valid_core_data.base}
build-base: {valid_core_data.build_base}
confinement: strict
grade: stable
grade: {valid_core_data.grade}
apps:
app1:
Expand All @@ -52,18 +79,19 @@ def test_expand_extensions_simple(new_dir, emitter):
file=yaml_file,
)

cmd = snapcraft.commands.core22.ExpandExtensionsCommand(None)
cmd = valid_core_data.command_class(None)
cmd.run(Namespace())
emitter.assert_message(
dedent(
"""\
f"""\
name: test-name
version: '0.1'
summary: testing extensions
description: expand a fake extension
base: core22
base: {valid_core_data.base}
build-base: {valid_core_data.build_base}
confinement: strict
grade: stable
grade: {valid_core_data.grade}
apps:
app1:
command: app1
Expand All @@ -84,7 +112,7 @@ def test_expand_extensions_simple(new_dir, emitter):


@pytest.mark.usefixtures("fake_extension")
def test_expand_extensions_complex(new_dir, emitter, mocker):
def test_expand_extensions_complex(new_dir, emitter, mocker, valid_core_data):
"""Expand an extension for a complex snapcraft.yaml file.
This includes parse-info, architectures, and advanced grammar.
Expand All @@ -97,14 +125,15 @@ def test_expand_extensions_complex(new_dir, emitter, mocker):
with Path("snapcraft.yaml").open("w") as yaml_file:
print(
dedent(
"""\
f"""\
name: test-name
version: "0.1"
summary: testing extensions
description: expand a fake extension
base: core22
base: {valid_core_data.base}
build-base: {valid_core_data.build_base}
confinement: strict
grade: stable
grade: {valid_core_data.grade}
architectures: [amd64, arm64, armhf]
apps:
Expand All @@ -128,18 +157,19 @@ def test_expand_extensions_complex(new_dir, emitter, mocker):
file=yaml_file,
)

cmd = snapcraft.commands.core22.ExpandExtensionsCommand(None)
cmd = valid_core_data.command_class(None)
cmd.run(Namespace())
emitter.assert_message(
dedent(
"""\
f"""\
name: test-name
version: '0.1'
summary: testing extensions
description: expand a fake extension
base: core22
base: {valid_core_data.base}
build-base: {valid_core_data.build_base}
confinement: strict
grade: stable
grade: {valid_core_data.grade}
apps:
app1:
command: app1
Expand Down
4 changes: 3 additions & 1 deletion tests/unit/commands/test_list_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
[
snapcraft.commands.core22.ListExtensionsCommand,
snapcraft.commands.core22.ExtensionsCommand,
snapcraft.commands.ListExtensions,
],
)
def test_command(emitter, command):
Expand All @@ -38,7 +39,7 @@ def test_command(emitter, command):
"""\
Extension name Supported bases
---------------------- ----------------------
fake-extension core22
fake-extension core22, core24
flutter-beta core18
flutter-dev core18
flutter-master core18
Expand Down Expand Up @@ -72,6 +73,7 @@ def test_command(emitter, command):
[
snapcraft.commands.core22.ListExtensionsCommand,
snapcraft.commands.core22.ExtensionsCommand,
snapcraft.commands.ListExtensions,
],
)
def test_command_extension_dups(emitter, command):
Expand Down
3 changes: 2 additions & 1 deletion tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class ExtensionImpl(extension.Extension):

@staticmethod
def get_supported_bases() -> Tuple[str, ...]:
return ("core22",)
return ("core22", "core24")

@staticmethod
def get_supported_confinement() -> Tuple[str, ...]:
Expand Down Expand Up @@ -394,6 +394,7 @@ def default_project(extra_project_params):
description="default project",
base="core24",
build_base="devel",
grade="devel",
parts=parts,
license="MIT",
**extra_project_params,
Expand Down
6 changes: 6 additions & 0 deletions tests/unit/models/test_projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,12 @@ def test_project_build_base_devel_grade_stable_error(self, project_yaml_data):
with pytest.raises(errors.ProjectValidationError, match=error):
Project.unmarshal(project_yaml_data(build_base="devel", grade="stable"))

def test_project_development_base_error(self, project_yaml_data):
error = "build-base must be 'devel' when base is 'core24'"

with pytest.raises(errors.ProjectValidationError, match=error):
Project.unmarshal(project_yaml_data(base="core24"))

def test_project_global_plugs_warning(self, project_yaml_data, emitter):
data = project_yaml_data(plugs={"desktop": None, "desktop-legacy": None})
Project.unmarshal(data)
Expand Down
Loading

0 comments on commit 623bb03

Please sign in to comment.