Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(project): improve error message for unsupported architectures #4392

Merged
merged 2 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions snapcraft/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
convert_architecture_deb_to_platform,
get_effective_base,
get_host_architecture,
get_supported_architectures,
is_architecture_supported,
)


Expand Down Expand Up @@ -119,6 +121,19 @@ def _validate_architectures(architectures):
)
unique_build_fors.add(architecture)

# validate architectures are supported
if len(architectures):
for element in architectures:
for arch in element.build_for + element.build_on:
if arch != "all" and not is_architecture_supported(arch):
supported_archs = utils.humanize_list(
get_supported_architectures(), "and"
)
raise ValueError(
f"Architecture {arch!r} is not supported. Supported "
f"architectures are {supported_archs}."
)

return architectures


Expand Down
18 changes: 18 additions & 0 deletions snapcraft/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,24 @@ def get_host_architecture():
)


def is_architecture_supported(architecture: str) -> bool:
"""Check if an debian-syntax architecture is supported.

:param architecture: architecture to check

:returns: True if the architecture is supported by snapcraft.
"""
return architecture in list(_ARCH_TRANSLATIONS_DEB_TO_PLATFORM)


def get_supported_architectures() -> List[str]:
"""Get a list of architectures supported by snapcraft.

:returns: A list of architectures.
"""
return list(_ARCH_TRANSLATIONS_DEB_TO_PLATFORM.keys())


def convert_architecture_deb_to_platform(architecture: str) -> str:
"""Convert an architecture from deb/snap syntax to platform syntax.

Expand Down
42 changes: 21 additions & 21 deletions tests/unit/parts/test_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -1537,49 +1537,49 @@ def test_root_packages(minimal_yaml_data, key, value):
def test_get_build_plan_single_element_matching(snapcraft_yaml, mocker, new_dir):
"""Test get_build_plan with a single matching element."""
mocker.patch(
"snapcraft.parts.lifecycle.get_host_architecture", return_value="aarch64"
"snapcraft.parts.lifecycle.get_host_architecture", return_value="arm64"
)
yaml_data = {
"base": "core22",
"architectures": [{"build-on": "aarch64", "build-for": "aarch64"}],
"architectures": [{"build-on": "arm64", "build-for": "arm64"}],
}

snapcraft_yaml_data = snapcraft_yaml(**yaml_data)

assert parts_lifecycle.get_build_plan(
snapcraft_yaml_data, parsed_args=argparse.Namespace(build_for=None)
) == [("aarch64", "aarch64")]
) == [("arm64", "arm64")]


def test_get_build_plan_build_for_all(snapcraft_yaml, mocker, new_dir):
"""Test get_build_plan with `build-for: all`."""
mocker.patch(
"snapcraft.parts.lifecycle.get_host_architecture", return_value="aarch64"
"snapcraft.parts.lifecycle.get_host_architecture", return_value="arm64"
)
yaml_data = {
"base": "core22",
"architectures": [{"build-on": "aarch64", "build-for": "all"}],
"architectures": [{"build-on": "arm64", "build-for": "all"}],
}

snapcraft_yaml_data = snapcraft_yaml(**yaml_data)

assert parts_lifecycle.get_build_plan(
snapcraft_yaml_data, parsed_args=argparse.Namespace(build_for=None)
) == [("aarch64", "all")]
) == [("arm64", "all")]


def test_get_build_plan_with_matching_elements(snapcraft_yaml, mocker, new_dir):
"""The build plan should only contain builds where `build-on` matches
the host architecture.
"""
mocker.patch(
"snapcraft.parts.lifecycle.get_host_architecture", return_value="aarch64"
"snapcraft.parts.lifecycle.get_host_architecture", return_value="arm64"
)
yaml_data = {
"base": "core22",
"architectures": [
{"build-on": "aarch64", "build-for": "aarch64"},
{"build-on": "aarch64", "build-for": "arm64"},
{"build-on": "arm64", "build-for": "amd64"},
{"build-on": "arm64", "build-for": "arm64"},
{"build-on": "armhf", "build-for": "armhf"},
],
}
Expand All @@ -1589,8 +1589,8 @@ def test_get_build_plan_with_matching_elements(snapcraft_yaml, mocker, new_dir):
assert parts_lifecycle.get_build_plan(
snapcraft_yaml_data, parsed_args=argparse.Namespace(build_for=None)
) == [
("aarch64", "aarch64"),
("aarch64", "arm64"),
("arm64", "amd64"),
("arm64", "arm64"),
]


Expand All @@ -1599,7 +1599,7 @@ def test_get_build_plan_list_without_matching_element(snapcraft_yaml, mocker, ne
the host architecture.
"""
mocker.patch(
"snapcraft.parts.lifecycle.get_host_architecture", return_value="aarch64"
"snapcraft.parts.lifecycle.get_host_architecture", return_value="arm64"
)
yaml_data = {
"base": "core22",
Expand All @@ -1621,21 +1621,21 @@ def test_get_build_plan_list_with_matching_element_and_env_var(
):
"""The build plan should be filtered down when `SNAPCRAFT_BUILD_FOR` is defined."""
mocker.patch(
"snapcraft.parts.lifecycle.get_host_architecture", return_value="aarch64"
"snapcraft.parts.lifecycle.get_host_architecture", return_value="arm64"
)
yaml_data = {
"base": "core22",
"architectures": [
{"build-on": "aarch64", "build-for": "aarch64"},
{"build-on": "aarch64", "build-for": "armhf"},
{"build-on": "arm64", "build-for": "arm64"},
{"build-on": "arm64", "build-for": "armhf"},
],
}

snapcraft_yaml_data = snapcraft_yaml(**yaml_data)

assert parts_lifecycle.get_build_plan(
snapcraft_yaml_data, parsed_args=argparse.Namespace(build_for="aarch64")
) == [("aarch64", "aarch64")]
snapcraft_yaml_data, parsed_args=argparse.Namespace(build_for="arm64")
) == [("arm64", "arm64")]


def test_get_build_plan_list_without_matching_element_and_build_for_arg(
Expand All @@ -1644,21 +1644,21 @@ def test_get_build_plan_list_without_matching_element_and_build_for_arg(
"""The build plan should be empty when no plan has a matching `build_for`
matching `SNAPCRAFT_BUILD_FOR.`"""
mocker.patch(
"snapcraft.parts.lifecycle.get_host_architecture", return_value="aarch64"
"snapcraft.parts.lifecycle.get_host_architecture", return_value="arm64"
)
yaml_data = {
"base": "core22",
"architectures": [
{"build-on": "aarch64", "build-for": "aarch64"},
{"build-on": "aarch64", "build-for": "armhf"},
{"build-on": "arm64", "build-for": "arm64"},
{"build-on": "arm64", "build-for": "armhf"},
],
}

snapcraft_yaml_data = snapcraft_yaml(**yaml_data)

assert (
parts_lifecycle.get_build_plan(
snapcraft_yaml_data, parsed_args=argparse.Namespace(build_for="arm64")
snapcraft_yaml_data, parsed_args=argparse.Namespace(build_for="amd64")
)
== []
)
Expand Down
18 changes: 18 additions & 0 deletions tests/unit/test_projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -1679,6 +1679,24 @@ def test_architecture_multiple_build_for_same_architecture_implicit(
error.value
)

@pytest.mark.parametrize(
"architectures",
[
"unknown",
{"build-on": ["unknown"]},
{"build-on": ["unknown"], "build-for": ["amd64"]},
{"build-on": ["amd64"], "build-for": ["unknown"]},
],
)
def test_architecture_unsupported(self, architectures, project_yaml_data):
"""Raise an error for unsupported architectures."""
data = project_yaml_data(architectures=[architectures])

with pytest.raises(errors.ProjectValidationError) as error:
Project.unmarshal(data)

assert "Architecture 'unknown' is not supported." in str(error.value)

def test_project_get_build_on(self, project_yaml_data):
"""Test `get_build_on()` returns the build-on string."""
data = project_yaml_data(
Expand Down
32 changes: 32 additions & 0 deletions tests/unit/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,38 @@ def test_process_version_git(mocker):
assert utils.process_version("git") == "1.2.3-dirty"


###########################
# Supported architectures #
###########################


@pytest.mark.parametrize("arch", utils.get_supported_architectures())
def test_is_architecture_supported(arch):
"""Supported architectures should return true."""
assert utils.is_architecture_supported(arch)


def test_is_architecture_not_supported():
"""Unsupported architectures should return false."""
assert not utils.is_architecture_supported("unknown")


def get_supported_architectures():
"""Validate list of supported architectures."""
supported_archs = utils.get_supported_architectures()

assert supported_archs == [
"arm64",
"armhf",
"i386",
"powerpc",
"ppc64el",
"amd64",
"s390x",
"riscv64",
]


#########################
# Convert Architectures #
#########################
Expand Down