Skip to content
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
7 changes: 7 additions & 0 deletions craft_application/_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ class ConfigModel(pydantic.BaseModel):
"""The platform for which to build."""
build_for: str | None = None
"""The target architecture for which to build."""
build_on: str | None = None
"""The architecture on which to build.

This is ignored in destructive mode and is only used for launching a provider.
The provider is instructed about the requested architecture. If that architecture
cannot run with the current system configuration, it will raise an error.
"""

parallel_build_count: int
"""The parallel build count to send to Craft Parts.
Expand Down
3 changes: 3 additions & 0 deletions craft_application/services/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ def instance(
if clean_existing:
self._clean_instance(provider, work_dir, build_info, project_name)

build_on = self._services.get("config").get("build_on")

emit.progress(f"Launching managed {base_name[0]} {base_name[1]} instance...")
with provider.launched_environment(
project_name=project_name,
Expand All @@ -225,6 +227,7 @@ def instance(
use_base_instance=use_base_instance,
prepare_instance=prepare_instance,
shutdown_delay_mins=shutdown_delay,
instance_architecture=build_on,
Comment thread
lengau marked this conversation as resolved.
) as instance:
Comment thread
lengau marked this conversation as resolved.
instance.mount(
host_source=work_dir,
Expand Down
11 changes: 10 additions & 1 deletion docs/reference/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,16 @@ Changelog
6.3.0 (unreleased)
------------------

Testing
Providers
=========

- Craft Application can now build on a provider using a different architecture
from the host architecture as long as the provider supports that architecture.
Set the ``CRAFT_BUILD_ON`` environment variable to an architecture to use
this.

Commands
========
Comment thread
lengau marked this conversation as resolved.

- The ``test`` command now reads ``spread.yaml`` before beginning the lifecycle,
erroring early if the file is invalid.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ dependencies = [
"craft-grammar>=2.3.0",
"craft-parts>=2.28.0",
"craft-platforms>=0.6.0",
"craft-providers>=3.3.0",
"craft-providers>=3.5.0",
"Jinja2>=3.1.6,<4.0.0",
"snap-helpers>=0.4.2",
"platformdirs>=3.10",
Expand Down
8 changes: 7 additions & 1 deletion spread.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ backends:
workers: 4
memory: 4G
storage: 20G
# - ubuntu-24.04-arm-64:
# image: ubuntu-os-cloud/ubuntu-2404-lts-arm64
# workers: 4
# memory: 4G
# storage: 20G
Comment thread
lengau marked this conversation as resolved.

# lxd: # Disabled due to https://github.com/canonical/spread/issues/215
# systems:
# - ubuntu-24.04
Expand Down Expand Up @@ -143,7 +149,7 @@ prepare: |
sudo snap install snapd || sudo snap refresh snapd
sudo snap wait system seed.loaded

sudo snap install --dangerous --classic tests/spread/*.snap
sudo snap install --dangerous --classic tests/spread/*$(dpkg --print-architecture).snap
Comment thread
lengau marked this conversation as resolved.

# Select older LXD channels for older bases.
. /etc/os-release
Expand Down
88 changes: 88 additions & 0 deletions tests/integration/services/test_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@
"""Integration tests for provider service."""

import contextlib
import pathlib
import subprocess

import craft_platforms
import craft_providers
import pytest
from craft_application import ProviderService


@pytest.mark.parametrize(
Expand Down Expand Up @@ -142,3 +144,89 @@ def test_run_managed(provider_service, fake_services, fetch, snap_safe_tmp_path)
provider_service.run_managed(
build_info, enable_fetch_service=fetch, command=["echo", "hi"]
)


@pytest.mark.slow
@pytest.mark.parametrize(
"build_on",
[
pytest.param(
"riscv64",
marks=pytest.mark.skipif(
craft_platforms.DebianArchitecture.from_host()
== craft_platforms.DebianArchitecture.RISCV64,
reason="Not testing on compatible host.",
),
),
pytest.param(
"s390x",
marks=pytest.mark.skipif(
craft_platforms.DebianArchitecture.from_host()
== craft_platforms.DebianArchitecture.S390X,
reason="Not testing on compatible host.",
Comment thread
lengau marked this conversation as resolved.
),
),
],
)
@pytest.mark.parametrize("base", [craft_platforms.DistroBase("ubuntu", "26.04")])
Comment thread
lengau marked this conversation as resolved.
def test_get_incompatible_instance_error(
monkeypatch: pytest.MonkeyPatch,
tmp_path: pathlib.Path,
provider_service: ProviderService,
build_on: str,
base: craft_platforms.DistroBase,
):
monkeypatch.setenv("CRAFT_BUILD_ON", build_on)

build_info = craft_platforms.BuildInfo(
platform="foo",
build_on=craft_platforms.DebianArchitecture(build_on),
build_for=craft_platforms.DebianArchitecture.from_host(),
build_base=base,
)

with pytest.raises(
craft_providers.errors.ProviderError,
match="Requested architecture isn't supported by this host",
Comment thread
mr-cal marked this conversation as resolved.
):
with provider_service.instance(build_info, work_dir=tmp_path):
pass


@pytest.mark.slow
@pytest.mark.parametrize(
"build_on",
[
pytest.param(
"armhf",
marks=pytest.mark.skipif(
craft_platforms.DebianArchitecture.from_host()
!= craft_platforms.DebianArchitecture.ARM64,
reason="Skipping incompatible host.",
),
),
],
)
@pytest.mark.parametrize("base", [craft_platforms.DistroBase("ubuntu", "26.04")])
def test_instance_with_different_architecture(
Comment thread
lengau marked this conversation as resolved.
monkeypatch: pytest.MonkeyPatch,
tmp_path: pathlib.Path,
provider_service: ProviderService,
build_on: str,
base: craft_platforms.DistroBase,
):
monkeypatch.setenv("CRAFT_BUILD_ON", build_on)

build_info = craft_platforms.BuildInfo(
platform="foo",
build_on=craft_platforms.DebianArchitecture(build_on),
build_for=craft_platforms.DebianArchitecture.from_host(),
build_base=base,
)

with provider_service.instance(build_info, work_dir=tmp_path) as instance:
result = instance.execute_run(
["dpkg", "--print-architecture"], text=True, capture_output=True
)

assert result.stdout.rstrip() == build_on
17 changes: 17 additions & 0 deletions tests/spread/testcraft/different-architecture/task.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
summary: Testcraft can build on armhf on an arm64 runner

priority: 100

systems:
- ubuntu-24.04-arm-64
Comment thread
lengau marked this conversation as resolved.

execute: |
# Configure testcraft to build on armhf
export CRAFT_BUILD_ON=armhf

testcraft pack --verbosity=debug
test -f built-on-armhf-0.1-platform-independent.testcraft

restore: |
testcraft clean
rm -f built-on-armhf*.testcraft
16 changes: 16 additions & 0 deletions tests/spread/testcraft/different-architecture/testcraft.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: built-on-armhf
summary: A testcraft package that gets built on an armhf runner.
version: "0.1"

base: ubuntu@24.04
platforms:
platform-independent:
build-on: [armhf]
build-for: [all]

parts:
my-test:
plugin: nil
override-build: |
echo -e "#!/bin/bash\necho Hello, world!" > $CRAFT_PART_INSTALL/hello
chmod a+x $CRAFT_PART_INSTALL/hello
5 changes: 4 additions & 1 deletion tests/unit/services/test_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -663,8 +663,10 @@ def test_instance(
allow_unstable,
mock_provider,
):
# In case the user's system has this set.
# In case the user's system has these set.
monkeypatch.delenv("CRAFT_IDLE_MINS", raising=False)
monkeypatch.delenv("CRAFT_BUILD_ON", raising=False)
Comment thread
lengau marked this conversation as resolved.
monkeypatch.delenv(f"{app_metadata.name.upper()}_BUILD_ON", raising=False)
with provider_service.instance(
fake_build_info, work_dir=tmp_path, allow_unstable=allow_unstable
) as instance:
Expand All @@ -674,6 +676,7 @@ def test_instance(
mock_provider.launched_environment.assert_called_once_with(
project_name=fake_project.name,
project_path=tmp_path,
instance_architecture=None,
instance_name=mock.ANY,
base_configuration=mock.ANY,
allow_unstable=allow_unstable,
Expand Down
8 changes: 4 additions & 4 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading