Skip to content

Commit

Permalink
feat: add craft-platforms compatibility methods (#495)
Browse files Browse the repository at this point in the history
Also warns about some pending deprecations.
  • Loading branch information
lengau authored Oct 1, 2024
1 parent 327ff94 commit 3bd339c
Show file tree
Hide file tree
Showing 13 changed files with 153 additions and 26 deletions.
81 changes: 65 additions & 16 deletions craft_application/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@
"""
import abc
import dataclasses
import warnings
from collections.abc import Mapping
from typing import Annotated, Any
from typing import Annotated, Any, cast

import craft_parts
import craft_platforms
import craft_providers.bases
import pydantic
from craft_cli import emit
from craft_providers import bases
from craft_providers.errors import BaseConfigurationError
from typing_extensions import Self

from craft_application import errors
from craft_application.models import base
Expand Down Expand Up @@ -86,6 +89,30 @@ class BuildInfo:
base: craft_providers.bases.BaseName
"""The base to build on."""

def __post_init__(self) -> None:
warnings.warn(
"BuildInfo is pending deprecation and will be replaced with craft_platforms.BuildInfo.",
PendingDeprecationWarning,
stacklevel=2,
)

@classmethod
def from_platforms(cls, info: craft_platforms.BuildInfo) -> Self:
"""Convert a craft-platforms BuildInfo to a craft-application BuildInfo."""
build_for = (
"all"
if info.build_for == "all"
else craft_platforms.DebianArchitecture(info.build_for)
)
return cls(
platform=info.platform,
build_on=craft_platforms.DebianArchitecture(info.build_on),
build_for=build_for,
base=craft_providers.bases.BaseName(
name=info.build_base.distribution, version=info.build_base.series
),
)


class Platform(base.CraftBaseModel):
"""Project platform definition."""
Expand Down Expand Up @@ -127,6 +154,18 @@ def _validate_platform_set(

return values

@classmethod
def from_platforms(cls, platforms: craft_platforms.Platforms) -> dict[str, Self]:
"""Create a dictionary ofthese objects from craft_platforms PlatformDicts."""
result: dict[str, Self] = {}
for key, value in platforms.items():
name = str(key)
platform = (
{"build-on": [name], "build-for": [name]} if value is None else value
)
result[name] = cls.model_validate(platform)
return result


def _populate_platforms(platforms: dict[str, Any]) -> dict[str, Any]:
"""Populate empty platform entries.
Expand Down Expand Up @@ -160,6 +199,15 @@ class BuildPlanner(base.CraftBaseModel, metaclass=abc.ABCMeta):
base: str | None = None
build_base: str | None = None

@pydantic.model_validator(mode="after")
def _warn_deprecation(self) -> Self:
warnings.warn(
"The craft-application BuildPlanner is pending deprecation in favour of functions that create build plans in craft-platforms.",
PendingDeprecationWarning,
stacklevel=2,
)
return self

@pydantic.field_validator("platforms", mode="before")
@classmethod
def _populate_platforms(cls, platforms: dict[str, Any]) -> dict[str, Any]:
Expand Down Expand Up @@ -214,21 +262,22 @@ def effective_base(self) -> bases.BaseName:

def get_build_plan(self) -> list[BuildInfo]:
"""Obtain the list of architectures and bases from the Project."""
build_infos: list[BuildInfo] = []

for platform_label, platform in self.platforms.items():
for build_for in platform.build_for or [platform_label]:
for build_on in platform.build_on or [platform_label]:
build_infos.append(
BuildInfo(
platform=platform_label,
build_on=build_on,
build_for=build_for,
base=self.effective_base,
)
)

return build_infos
effective_base = self.effective_base
base = craft_platforms.DistroBase(
distribution=effective_base.name, series=effective_base.version
)
platforms = cast(
craft_platforms.Platforms,
{key: value.marshal() for key, value in self.platforms.items()},
)

return [
BuildInfo.from_platforms(info)
for info in craft_platforms.get_platforms_build_plan(
base=base,
platforms=platforms,
)
]


def _validate_package_repository(repository: dict[str, Any]) -> dict[str, Any]:
Expand Down
2 changes: 1 addition & 1 deletion craft_application/services/lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def _get_build_for(self) -> str:
# something else like clean() is called).
# We also use the host arch if the build-for is 'all'
if self._build_plan and self._build_plan[0].build_for != "all":
return self._build_plan[0].build_for
return str(self._build_plan[0].build_for)
return util.get_host_architecture()

def _init_lifecycle_manager(self) -> LifecycleManager:
Expand Down
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ dependencies = [
"craft-cli>=2.6.0",
"craft-grammar>=2.0.0",
"craft-parts>=2.0.0",
"craft-platforms>=0.3.1",
"craft-providers>=2.0.0",
"snap-helpers>=0.4.2",
"platformdirs>=3.10",
Expand Down Expand Up @@ -142,6 +143,10 @@ xfail_strict = true
markers = [
"enable_features: Tests that require specific features",
]
filterwarnings = [
"ignore:The craft-application BuildPlanner:PendingDeprecationWarning",
"ignore:BuildInfo:PendingDeprecationWarning",
]

[tool.coverage.run]
branch = true
Expand Down
1 change: 0 additions & 1 deletion tests/integration/data/build-secrets/testcraft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ platforms:
arm64:
armhf:
i386:
powerpc:
ppc64el:
riscv64:
s390x:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ platforms:
arm64:
armhf:
i386:
powerpc:
ppc64el:
riscv64:
s390x:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ platforms:
arm64:
armhf:
i386:
powerpc:
ppc64el:
riscv64:
s390x:
Expand Down
1 change: 0 additions & 1 deletion tests/integration/data/valid_projects/basic/testcraft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ platforms:
arm64:
armhf:
i386:
powerpc:
ppc64el:
riscv64:
s390x:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version: 1.0
base: "ubuntu@22.04"
platforms:
platform1:
build-on: [amd64, arm64, armhf, i386, powerpc, ppc64el, riscv64, s390x]
build-on: [amd64, arm64, armhf, i386, ppc64el, riscv64, s390x]
build-for: [all]

parts:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ platforms:
arm64:
armhf:
i386:
powerpc:
ppc64el:
riscv64:
s390x:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ platforms:
arm64:
armhf:
i386:
powerpc:
ppc64el:
riscv64:
s390x:
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def test_project_managed(capsys, monkeypatch, tmp_path, project, create_app):
app = create_app()
app._work_dir = tmp_path

app.run()
assert app.run() == 0

assert (tmp_path / "package_1.0.tar.zst").exists()
captured = capsys.readouterr()
Expand Down
78 changes: 78 additions & 0 deletions tests/unit/models/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import textwrap
from textwrap import dedent

import craft_platforms
import craft_providers.bases
import pydantic
import pytest
Expand Down Expand Up @@ -123,6 +124,43 @@ def full_project_dict():
return copy.deepcopy(FULL_PROJECT_DICT)


@pytest.mark.parametrize(
("incoming", "expected"),
[
(
craft_platforms.BuildInfo(
"my-platform",
craft_platforms.DebianArchitecture.RISCV64,
"all",
craft_platforms.DistroBase("ubuntu", "24.04"),
),
BuildInfo(
"my-platform",
"riscv64",
"all",
craft_providers.bases.BaseName("ubuntu", "24.04"),
),
),
(
craft_platforms.BuildInfo(
"my-platform",
craft_platforms.DebianArchitecture.RISCV64,
craft_platforms.DebianArchitecture.AMD64,
craft_platforms.DistroBase("almalinux", "9"),
),
BuildInfo(
"my-platform",
"riscv64",
"amd64",
craft_providers.bases.BaseName("almalinux", "9"),
),
),
],
)
def test_build_info_from_platforms(incoming, expected):
assert BuildInfo.from_platforms(incoming) == expected


@pytest.mark.parametrize(
("incoming", "expected"),
[
Expand Down Expand Up @@ -155,6 +193,46 @@ def test_platform_vectorise_architectures(incoming, expected):
assert platform == expected


@pytest.mark.parametrize(
("incoming", "expected"),
[
(
{"build-on": ["amd64"], "build-for": ["all"]},
Platform(build_on=["amd64"], build_for=["all"]),
),
],
)
def test_platform_from_platform_dict(incoming, expected):
assert Platform.model_validate(incoming) == expected


@pytest.mark.parametrize(
("incoming", "expected"),
[
pytest.param(
{
craft_platforms.DebianArchitecture.AMD64: None,
craft_platforms.DebianArchitecture.ARM64: None,
craft_platforms.DebianArchitecture.RISCV64: None,
},
{
"amd64": Platform(build_on=["amd64"], build_for=["amd64"]),
"arm64": Platform(build_on=["arm64"], build_for=["arm64"]),
"riscv64": Platform(build_on=["riscv64"], build_for=["riscv64"]),
},
id="architectures",
),
pytest.param(
{"any string": {"build-on": ["amd64"], "build-for": ["all"]}},
{"any string": Platform(build_on=["amd64"], build_for=["all"])},
id="stringy",
),
],
)
def test_platform_from_platforms(incoming, expected):
assert Platform.from_platforms(incoming) == expected


@pytest.mark.parametrize(
("project_fixture", "project_dict"),
[("basic_project", BASIC_PROJECT_DICT), ("full_project", FULL_PROJECT_DICT)],
Expand Down
3 changes: 2 additions & 1 deletion tests/unit/services/test_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import craft_parts
import craft_parts.callbacks
import craft_platforms
import pytest
import pytest_check
from craft_application import errors, models, util
Expand Down Expand Up @@ -372,7 +373,7 @@ def test_get_primed_stage_packages(lifecycle_service):
BuildInfo(
"my-platform",
build_on="any",
build_for="amd64",
build_for=craft_platforms.DebianArchitecture.AMD64,
base=bases.BaseName("ubuntu", "24.04"),
)
],
Expand Down

0 comments on commit 3bd339c

Please sign in to comment.