Skip to content

Commit

Permalink
feat: allow for multi-base builds (#598)
Browse files Browse the repository at this point in the history
Signed-off-by: Callahan Kovacs <[email protected]>
  • Loading branch information
mr-cal authored Dec 19, 2024
1 parent 8dae7e2 commit d81c39a
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 28 deletions.
34 changes: 18 additions & 16 deletions craft_application/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
UniqueStrList,
VersionStr,
)
from craft_application.util import is_valid_architecture


@dataclasses.dataclass
Expand Down Expand Up @@ -131,13 +130,14 @@ def _vectorise_architectures(cls, values: str | list[str]) -> list[str]:
@pydantic.field_validator("build_on", "build_for", mode="after")
@classmethod
def _validate_architectures(cls, values: list[str]) -> list[str]:
"""Validate the architecture entries."""
for architecture in values:
if architecture != "all" and not is_valid_architecture(architecture):
raise errors.CraftValidationError(
f"Invalid architecture: {architecture!r} "
"must be a valid debian architecture."
)
"""Validate the architecture entries.
Entries must be a valid debian architecture or 'all'. Architectures may
be preceded by an optional base prefix formatted as '[<base>:]<arch>'.
:raises ValueError: If any of the bases or architectures are not valid.
"""
[craft_platforms.parse_base_and_architecture(arch) for arch in values]

return values

Expand Down Expand Up @@ -167,8 +167,10 @@ def from_platforms(cls, platforms: craft_platforms.Platforms) -> dict[str, Self]
return result


def _populate_platforms(platforms: dict[str, Any]) -> dict[str, Any]:
"""Populate empty platform entries.
def _expand_shorthand_platforms(platforms: dict[str, Any]) -> dict[str, Any]:
"""Expand shorthand platform entries into standard form.
Assumes the platform label is a valid as a build-on and build-for entry.
:param platforms: The platform data.
Expand Down Expand Up @@ -211,8 +213,8 @@ def _warn_deprecation(self) -> Self:
@pydantic.field_validator("platforms", mode="before")
@classmethod
def _populate_platforms(cls, platforms: dict[str, Any]) -> dict[str, Any]:
"""Populate empty platform entries."""
return _populate_platforms(platforms)
"""Expand shorthand platform entries into standard form."""
return _expand_shorthand_platforms(platforms)

@pydantic.field_validator("platforms", mode="after")
@classmethod
Expand All @@ -233,9 +235,9 @@ def _validate_platforms_all_keyword(

# validate `all` inside each platform:
for platform in platforms.values():
if platform.build_on and "all" in platform.build_on:
if platform and platform.build_on and "all" in platform.build_on:
raise ValueError("'all' cannot be used for 'build-on'")
if platform.build_for and "all" in platform.build_for:
if platform and platform.build_for and "all" in platform.build_for:
is_all_used = True

# validate `all` across all platforms:
Expand Down Expand Up @@ -337,8 +339,8 @@ class Project(base.CraftBaseModel):
@pydantic.field_validator("platforms", mode="before")
@classmethod
def _populate_platforms(cls, platforms: dict[str, Platform]) -> dict[str, Platform]:
"""Populate empty platform entries."""
return _populate_platforms(platforms)
"""Expand shorthand platform entries into standard form."""
return _expand_shorthand_platforms(platforms)

@property
def effective_base(self) -> Any: # noqa: ANN401 app specific classes can improve
Expand Down
8 changes: 8 additions & 0 deletions docs/reference/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
Changelog
*********

4.7.0 (2024-Dec-19)
-------------------

Application
===========

- Allow applications to implement multi-base build plans.

4.6.0 (2024-Dec-13)
-------------------

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ dependencies = [
"craft-cli>=2.12.0",
"craft-grammar>=2.0.0",
"craft-parts>=2.1.1",
"craft-platforms>=0.3.1",
"craft-platforms>=0.5.0",
"craft-providers>=2.0.4",
"Jinja2~=3.1",
"snap-helpers>=0.4.2",
Expand Down
30 changes: 19 additions & 11 deletions tests/unit/models/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
Project,
constraints,
)
from craft_application.util import platforms

PROJECTS_DIR = pathlib.Path(__file__).parent / "project_models"
PARTS_DICT = {"my-part": {"plugin": "nil"}}
Expand Down Expand Up @@ -170,15 +169,15 @@ def test_build_info_from_platforms(incoming, expected):
Platform(build_on=[arch], build_for=[arch]),
id=arch,
)
for arch in platforms._ARCH_TRANSLATIONS_DEB_TO_PLATFORM
for arch in craft_platforms.DebianArchitecture
),
*(
pytest.param(
{"build-on": arch},
Platform(build_on=[arch]),
id=f"build-on-only-{arch}",
)
for arch in platforms._ARCH_TRANSLATIONS_DEB_TO_PLATFORM
for arch in craft_platforms.DebianArchitecture
),
pytest.param(
{"build-on": "amd64", "build-for": "riscv64"},
Expand Down Expand Up @@ -562,30 +561,39 @@ def test_unmarshal_invalid_repositories(


@pytest.mark.parametrize("model", [Project, BuildPlanner])
def test_platform_invalid_arch(model, basic_project_dict):
basic_project_dict["platforms"] = {"unknown": None}
@pytest.mark.parametrize("platform_label", ["unknown", "[email protected]:unknown"])
def test_platform_invalid_arch(model, platform_label, basic_project_dict):
basic_project_dict["platforms"] = {platform_label: None}
project_path = pathlib.Path("myproject.yaml")

with pytest.raises(CraftValidationError) as error:
model.from_yaml_data(basic_project_dict, project_path)

assert error.value.args[0] == (
"Invalid architecture: 'unknown' must be a valid debian architecture."
"Bad myproject.yaml content:\n"
f"- 'unknown' is not a valid DebianArchitecture (in field 'platforms.{platform_label}.build-on')\n"
f"- 'unknown' is not a valid DebianArchitecture (in field 'platforms.{platform_label}.build-for')"
)


@pytest.mark.parametrize("model", [Project, BuildPlanner])
@pytest.mark.parametrize("arch", ["unknown", "[email protected]:unknown"])
@pytest.mark.parametrize("field_name", ["build-on", "build-for"])
def test_platform_invalid_build_arch(model, field_name, basic_project_dict):
basic_project_dict["platforms"] = {"amd64": {field_name: ["unknown"]}}
def test_platform_invalid_build_arch(model, arch, field_name, basic_project_dict):
basic_project_dict["platforms"] = {"amd64": {field_name: [arch]}}
project_path = pathlib.Path("myproject.yaml")

with pytest.raises(CraftValidationError) as error:
model.from_yaml_data(basic_project_dict, project_path)

assert error.value.args[0] == (
"Invalid architecture: 'unknown' must be a valid debian architecture."
)
error_lines = [
"Bad myproject.yaml content:",
"- field 'build-on' required in 'platforms.amd64' configuration",
f"- 'unknown' is not a valid DebianArchitecture (in field 'platforms.amd64.{field_name}')",
]
if field_name == "build-on":
error_lines.pop(1)
assert error.value.args[0] == "\n".join(error_lines)


@pytest.mark.parametrize(
Expand Down

0 comments on commit d81c39a

Please sign in to comment.