diff --git a/craft_application/__init__.py b/craft_application/__init__.py index 8c46c00d1..48fe2f7cf 100644 --- a/craft_application/__init__.py +++ b/craft_application/__init__.py @@ -30,6 +30,7 @@ ServiceFactory, ) from craft_application._config import ConfigModel +from craft_application.util import is_managed_mode try: from ._version import __version__ @@ -54,4 +55,5 @@ "PackageService", "ProviderService", "ServiceFactory", + "is_managed_mode", ] diff --git a/craft_application/application.py b/craft_application/application.py index 8faf06541..16a28b0ba 100644 --- a/craft_application/application.py +++ b/craft_application/application.py @@ -25,6 +25,7 @@ import subprocess import sys import traceback +import warnings from collections.abc import Iterable, Sequence from dataclasses import dataclass, field from functools import cached_property @@ -33,6 +34,7 @@ import craft_cli import craft_parts +import craft_platforms import craft_providers from craft_parts.plugins.plugins import PluginType from platformdirs import user_cache_path @@ -152,7 +154,7 @@ def __init__( # This may be overridden by specific application implementations. self.project_dir = pathlib.Path.cwd() - if self.is_managed(): + if util.is_managed_mode(): self._work_dir = pathlib.Path("/root") else: self._work_dir = pathlib.Path.cwd() @@ -280,7 +282,7 @@ def _merge_defaults( @property def log_path(self) -> pathlib.Path | None: """Get the path to this process's log file, if any.""" - if self.is_managed(): + if util.is_managed_mode(): return util.get_managed_logpath(self.app) return None @@ -381,7 +383,7 @@ def get_project( with project_path.open() as file: yaml_data = util.safe_yaml_load(file) - host_arch = util.get_host_architecture() + host_arch = craft_platforms.DebianArchitecture.from_host().value build_planner = self.app.BuildPlannerClass.from_yaml_data( yaml_data, project_path ) @@ -432,7 +434,13 @@ def project(self) -> models.Project: def is_managed(self) -> bool: """Shortcut to tell whether we're running in managed mode.""" - return self.services.get_class("provider").is_managed() + warnings.warn( + DeprecationWarning( + "app.is_managed is deprecated. Use craft_application.is_managed_mode() instead." + ), + stacklevel=2, + ) + return util.is_managed_mode() def run_managed(self, platform: str | None, build_for: str | None) -> None: """Run the application in a managed instance.""" @@ -604,7 +612,7 @@ def _pre_run(self, dispatcher: craft_cli.Dispatcher) -> None: # Some commands might have a project_dir parameter. Those commands and # only those commands should get a project directory, but only when # not managed. - if self.is_managed(): + if util.is_managed_mode(): self.project_dir = pathlib.Path("/root/project") elif project_dir := getattr(args, "project_dir", None): self.project_dir = pathlib.Path(project_dir).expanduser().resolve() @@ -669,7 +677,7 @@ def _run_inner(self) -> int: # command runs in the outer instance craft_cli.emit.debug(f"Running {self.app.name} {command.name} on host") return_code = dispatcher.run() or os.EX_OK - elif not self.is_managed(): + elif not util.is_managed_mode(): # command runs in inner instance, but this is the outer instance self.run_managed(platform, build_for) return_code = os.EX_OK @@ -735,7 +743,7 @@ def _emit_error( error.__cause__ = cause # Do not report the internal logpath if running inside an instance - if self.is_managed(): + if util.is_managed_mode(): error.logpath_report = False craft_cli.emit.error(error) @@ -843,7 +851,7 @@ def _set_global_environment(self, info: craft_parts.ProjectInfo) -> None: def _render_secrets(self, yaml_data: dict[str, Any]) -> None: """Render build-secrets, in-place.""" secret_values = secrets.render_secrets( - yaml_data, managed_mode=self.is_managed() + yaml_data, managed_mode=util.is_managed_mode() ) num_secrets = len(secret_values.secret_strings) diff --git a/craft_application/services/fetch.py b/craft_application/services/fetch.py index f50edda46..93c885e9f 100644 --- a/craft_application/services/fetch.py +++ b/craft_application/services/fetch.py @@ -84,7 +84,7 @@ def setup(self) -> None: """Start the fetch-service process with proper arguments.""" super().setup() - if not self._services.get_class("provider").is_managed(): + if not util.is_managed_mode(): # Early fail if the fetch-service is not installed. fetch.verify_installed() @@ -153,7 +153,7 @@ def create_project_manifest(self, artifacts: list[pathlib.Path]) -> None: Only supports a single generated artifact, and only in managed runs. """ - if not self._services.ProviderClass.is_managed(): + if not util.is_managed_mode(): emit.debug("Unable to generate the project manifest on the host.") return diff --git a/craft_application/services/provider.py b/craft_application/services/provider.py index 4405bc3f1..6ffa51ee0 100644 --- a/craft_application/services/provider.py +++ b/craft_application/services/provider.py @@ -24,6 +24,7 @@ import pkgutil import sys import urllib.request +import warnings from collections.abc import Generator, Iterable from pathlib import Path from typing import TYPE_CHECKING @@ -85,7 +86,13 @@ def __init__( @classmethod def is_managed(cls) -> bool: """Determine whether we're running in managed mode.""" - return os.getenv(cls.managed_mode_env_var) == "1" + warnings.warn( + DeprecationWarning( + "ProviderService.is_managed() is deprecated. Use craft_application.is_managed_mode() instead." + ), + stacklevel=2, + ) + return util.is_managed_mode() def setup(self) -> None: """Application-specific service setup.""" @@ -223,7 +230,7 @@ def get_provider(self, name: str | None = None) -> craft_providers.Provider: if self._provider is not None: return self._provider - if self.is_managed(): + if util.is_managed_mode(): raise CraftError("Cannot nest managed environments.") # (1) use provider specified in the function argument, diff --git a/craft_application/util/platforms.py b/craft_application/util/platforms.py index 8deee64ea..8e6fc26b3 100644 --- a/craft_application/util/platforms.py +++ b/craft_application/util/platforms.py @@ -20,9 +20,11 @@ import functools import os import platform +import warnings from typing import Final -from craft_parts.utils import os_utils +import craft_platforms +import distro from craft_providers import bases from .string import strtobool @@ -33,8 +35,13 @@ @functools.lru_cache(maxsize=1) def get_host_architecture() -> str: """Get host architecture in deb format.""" - machine = platform.machine() - return _ARCH_TRANSLATIONS_PLATFORM_TO_DEB.get(machine, machine) + warnings.warn( + DeprecationWarning( + "get_host_architecture() is deprecated. Use craft_platforms.DebianArchitecture.from_host()" + ), + stacklevel=2, + ) + return craft_platforms.DebianArchitecture.from_host().value def convert_architecture_deb_to_platform(arch: str) -> str: @@ -54,10 +61,16 @@ def is_valid_architecture(arch: str) -> bool: @functools.lru_cache(maxsize=1) def get_host_base() -> bases.BaseName: """Get the craft-providers base for the running host.""" - release = os_utils.OsRelease() - os_id = release.id() - version_id = release.version_id() - return bases.BaseName(os_id, version_id) + warnings.warn( + PendingDeprecationWarning( + "get_host_base() is pending deprecation. Use craft_platforms.DistroBase.from_linux_distribution(distro.LinuxDistribution())" + ), + stacklevel=2, + ) + distro_base = craft_platforms.DistroBase.from_linux_distribution( + distro.LinuxDistribution(include_lsb=False, include_uname=False) + ) + return bases.BaseName(distro_base.distribution, distro_base.series) def get_hostname(hostname: str | None = None) -> str: diff --git a/craft_application/util/string.py b/craft_application/util/string.py index 2913afa12..549fe6c7e 100644 --- a/craft_application/util/string.py +++ b/craft_application/util/string.py @@ -31,7 +31,7 @@ def strtobool(value: str) -> bool: value = value.strip().lower() if value in {"true", "t", "yes", "y", "on", "1"}: return True - if value in {"false", "f", "no", "n", "off", "0"}: + if value in {"false", "f", "no", "n", "off", "0", "", "none"}: return False raise ValueError(f"Invalid boolean value: {value}") diff --git a/docs/conf.py b/docs/conf.py index 527df7a9b..e9273ec10 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -39,7 +39,15 @@ extensions = [ "canonical_sphinx", + "sphinx.ext.intersphinx", ] + +intersphinx_mapping = { + "craft-platforms": ( + "https://canonical-craft-platforms.readthedocs-hosted.com/en/latest", + None, + ) +} # endregion # region Options for extensions diff --git a/docs/reference/changelog.rst b/docs/reference/changelog.rst index fc026206d..82206fdd6 100644 --- a/docs/reference/changelog.rst +++ b/docs/reference/changelog.rst @@ -4,6 +4,27 @@ Changelog ********* +4.10.0 (YYYY-Mon-DD) +-------------------- + +Application +=========== + +- Deprecate ``Application.is_managed()`` in favour of :func:`is_managed_mode()` + +Services +======== + +- Deprecate ``ProviderService.is_managed()`` in favour of :func:`is_managed_mode()` + +Utilities +========= + +- Deprecate ``get_host_architecture()`` in favour of + :external+craft-platforms:meth:`craft_platforms.DebianArchitecture.from_host()` +- Warn that ``get_host_base()`` is pending deprecation in favour of + :external+craft-platforms:meth:`craft_platforms.DistroBase.from_linux_distribution()` + 4.9.1 (2025-Feb-12) ------------------- @@ -23,7 +44,7 @@ Application =========== - Add a feature to allow `Python plugins - https://packaging.python.org/en/latest/guides/creating-and-discovering-plugins/>`_ + `_ to extend or modify the behaviour of applications that use craft-application as a framework. The plugin packages must be installed in the same virtual environment as the application. diff --git a/tests/conftest.py b/tests/conftest.py index d0643ad02..29983872a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -21,12 +21,14 @@ import pathlib import shutil import subprocess +import warnings from dataclasses import dataclass from importlib import metadata from typing import TYPE_CHECKING, Any from unittest.mock import Mock import craft_parts +import craft_platforms import jinja2 import pydantic import pytest @@ -45,8 +47,12 @@ def _create_fake_build_plan(num_infos: int = 1) -> list[models.BuildInfo]: """Create a build plan that is able to execute on the running system.""" - arch = util.get_host_architecture() - base = util.get_host_base() + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + arch = util.get_host_architecture() + with warnings.catch_warnings(): + warnings.simplefilter("ignore", PendingDeprecationWarning) + base = util.get_host_base() return [models.BuildInfo("foo", arch, arch, base)] * num_infos @@ -133,7 +139,7 @@ def app_metadata_docs(features) -> craft_application.AppMetadata: @pytest.fixture def fake_project() -> models.Project: - arch = util.get_host_architecture() + arch = craft_platforms.DebianArchitecture.from_host().value return models.Project( name="full-project", # pyright: ignore[reportArgumentType] title="A fully-defined project", # pyright: ignore[reportArgumentType] @@ -161,7 +167,7 @@ def fake_build_plan(request) -> list[models.BuildInfo]: @pytest.fixture def full_build_plan(mocker) -> list[models.BuildInfo]: """A big build plan with multiple bases and build-for targets.""" - host_arch = util.get_host_architecture() + host_arch = craft_platforms.DebianArchitecture.from_host().value build_plan = [] for release in ("20.04", "22.04", "24.04"): build_plan.extend( @@ -280,6 +286,8 @@ def pack( def metadata(self) -> models.BaseMetadata: return models.BaseMetadata() + services.ServiceFactory.register("package", FakePackageService) + return FakePackageService @@ -305,6 +313,7 @@ def __init__( **kwargs, ) + services.ServiceFactory.register("lifecycle", FakeLifecycleService) return FakeLifecycleService @@ -314,6 +323,7 @@ class FakeInitService(services.InitService): def _get_loader(self, template_dir: pathlib.Path) -> jinja2.BaseLoader: return FileSystemLoader(tmp_path / "templates" / template_dir) + services.ServiceFactory.register("init", FakeInitService) return FakeInitService @@ -324,6 +334,7 @@ class FakeRemoteBuild(services.RemoteBuildService): def _get_lp_client(self) -> launchpad.Launchpad: return Mock(spec=launchpad.Launchpad) + services.ServiceFactory.register("remote_build", FakeRemoteBuild) return FakeRemoteBuild @@ -337,10 +348,6 @@ def fake_services( fake_init_service_class, fake_remote_build_service_class, ): - services.ServiceFactory.register("package", fake_package_service_class) - services.ServiceFactory.register("lifecycle", fake_lifecycle_service_class) - services.ServiceFactory.register("init", fake_init_service_class) - services.ServiceFactory.register("remote_build", fake_remote_build_service_class) factory = services.ServiceFactory(app_metadata, project=fake_project) factory.update_kwargs( "lifecycle", work_dir=tmp_path, cache_dir=tmp_path / "cache", build_plan=[] diff --git a/tests/integration/services/test_service_factory.py b/tests/integration/services/test_service_factory.py index d48240b5f..0ded2b077 100644 --- a/tests/integration/services/test_service_factory.py +++ b/tests/integration/services/test_service_factory.py @@ -28,12 +28,11 @@ def test_gets_dataclass_services( fake_lifecycle_service_class, fake_provider_service_class, ): + services.ServiceFactory.register("lifecycle", fake_lifecycle_service_class) + services.ServiceFactory.register("provider", fake_provider_service_class) factory = services.ServiceFactory( app_metadata, project=fake_project, - PackageClass=fake_package_service_class, - LifecycleClass=fake_lifecycle_service_class, - ProviderClass=fake_provider_service_class, ) check.is_instance(factory.package, services.PackageService) @@ -49,7 +48,6 @@ def test_gets_registered_services( fake_lifecycle_service_class, fake_provider_service_class, ): - services.ServiceFactory.register("package", fake_package_service_class) services.ServiceFactory.register("lifecycle", fake_lifecycle_service_class) services.ServiceFactory.register("provider", fake_provider_service_class) factory = services.ServiceFactory( @@ -63,10 +61,10 @@ def test_gets_registered_services( def test_real_service_error(app_metadata, fake_project): + services.ServiceFactory.register("package", services.PackageService) factory = services.ServiceFactory( app_metadata, project=fake_project, - PackageClass=services.PackageService, ) with pytest.raises( diff --git a/tests/integration/test_application.py b/tests/integration/test_application.py index 406ee327a..5853c2284 100644 --- a/tests/integration/test_application.py +++ b/tests/integration/test_application.py @@ -29,7 +29,7 @@ from craft_application.util import yaml -class TestableApplication(craft_application.Application): +class FakeApplication(craft_application.Application): """An application modified for integration tests. Modifications are: @@ -38,7 +38,7 @@ class TestableApplication(craft_application.Application): def _pre_run(self, dispatcher: craft_cli.Dispatcher) -> None: super()._pre_run(dispatcher) - if self.is_managed(): + if util.is_managed_mode(): self.project_dir = pathlib.Path.cwd() @@ -47,10 +47,8 @@ def create_app(app_metadata, fake_package_service_class): def _inner(): # Create a factory without a project, to simulate a real application use # and force loading from disk. - services = craft_application.ServiceFactory( - app_metadata, PackageClass=fake_package_service_class - ) - return TestableApplication(app_metadata, services) + services = craft_application.ServiceFactory(app_metadata) + return FakeApplication(app_metadata, services) return _inner @@ -143,7 +141,7 @@ class _FakeCommand(craft_application.commands.AppCommand): ids=lambda ordered: f"keep_order={ordered}", ) def test_registering_new_commands( - app: TestableApplication, + app: FakeApplication, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], *, @@ -456,7 +454,7 @@ def test_build_secrets_managed( app = setup_secrets_project(destructive_mode=False) monkeypatch.setenv("CRAFT_MANAGED_MODE", "1") - assert app.is_managed() + assert util.is_managed_mode() app._work_dir = tmp_path # Before running the application, configure its environment "as if" the host app diff --git a/tests/unit/services/test_fetch.py b/tests/unit/services/test_fetch.py index 51748b2fe..bdae19639 100644 --- a/tests/unit/services/test_fetch.py +++ b/tests/unit/services/test_fetch.py @@ -37,7 +37,7 @@ from craft_providers import bases from freezegun import freeze_time -from craft_application import ProviderService, fetch, services +from craft_application import fetch, services, util from craft_application.models import BuildInfo from craft_application.services import fetch as service_module @@ -180,7 +180,7 @@ def test_warning_experimental(mocker, fetch_service, run_on_host, emitter): mocker.patch.object(fetch, "start_service") mocker.patch.object(fetch, "verify_installed") mocker.patch.object(fetch, "_get_service_base_dir", return_value=pathlib.Path()) - mocker.patch.object(ProviderService, "is_managed", return_value=not run_on_host) + mocker.patch.object(util, "is_managed_mode", return_value=not run_on_host) fetch_service.setup() @@ -197,7 +197,7 @@ def test_warning_experimental(mocker, fetch_service, run_on_host, emitter): def test_setup_managed(mocker, fetch_service): """The fetch-service process should only be checked/started when running on the host.""" mock_start = mocker.patch.object(fetch, "start_service") - mocker.patch.object(ProviderService, "is_managed", return_value=True) + mocker.patch.object(util, "is_managed_mode", return_value=True) fetch_service.setup() diff --git a/tests/unit/services/test_provider.py b/tests/unit/services/test_provider.py index f53f7dce3..f4ca14cc4 100644 --- a/tests/unit/services/test_provider.py +++ b/tests/unit/services/test_provider.py @@ -207,7 +207,10 @@ def test_is_managed(managed_value, expected, monkeypatch): provider.ProviderService.managed_mode_env_var, str(managed_value) ) - assert provider.ProviderService.is_managed() == expected + with pytest.warns( + DeprecationWarning, match=r"ProviderService.is_managed\(\) is deprecated." + ): + assert provider.ProviderService.is_managed() == expected def test_forward_environment_variables(monkeypatch, provider_service): @@ -307,20 +310,20 @@ def test_get_existing_provider(self, provider_service): assert provider_service.get_provider() == expected - def test_get_provider_managed_mode(self, provider_service): + def test_get_provider_managed_mode(self, provider_service, monkeypatch): """Raise an error when running in managed mode.""" provider_service._provider = None - provider_service.is_managed = lambda: True + monkeypatch.setattr("craft_application.util.is_managed_mode", lambda: True) with pytest.raises(errors.CraftError) as raised: provider_service.get_provider() assert raised.value == errors.CraftError("Cannot nest managed environments.") - def test_get_provider_from_argument(self, provider_service, providers): + def test_get_provider_from_argument(self, provider_service, providers, monkeypatch): """(1) use provider specified in the function argument.""" provider_service._provider = None - provider_service.is_managed = lambda: False + monkeypatch.setattr("craft_application.util.is_managed_mode", lambda: False) result = provider_service.get_provider(name=providers.name) @@ -329,7 +332,7 @@ def test_get_provider_from_argument(self, provider_service, providers): def test_get_provider_from_env(self, monkeypatch, provider_service, providers): """(2) get the provider from the environment (CRAFT_BUILD_ENVIRONMENT).""" provider_service._provider = None - provider_service.is_managed = lambda: False + monkeypatch.setattr("craft_application.util.is_managed_mode", lambda: False) monkeypatch.setenv("CRAFT_BUILD_ENVIRONMENT", providers.name) result = provider_service.get_provider() @@ -339,7 +342,7 @@ def test_get_provider_from_env(self, monkeypatch, provider_service, providers): def test_get_provider_from_snap(self, mocker, provider_service, providers): """(3) use provider specified with snap configuration.""" provider_service._provider = None - provider_service.is_managed = lambda: False + mocker.patch("craft_application.util.is_managed_mode", return_value=False) mocker.patch( "craft_application.services.provider.snap_config.get_snap_config", return_value=snap_config.SnapConfig(provider=providers.name), @@ -354,7 +357,7 @@ def test_get_provider_no_snap_config(self, mocker, provider_service, emitter): Instead, proceed to the next step.""" provider_service._provider = None - provider_service.is_managed = lambda: False + mocker.patch("craft_application.util.is_managed_mode", return_value=False) mocker.patch( "craft_application.services.provider.snap_config.get_snap_config", return_value=None, diff --git a/tests/unit/test_application.py b/tests/unit/test_application.py index 4956711c3..90b203bea 100644 --- a/tests/unit/test_application.py +++ b/tests/unit/test_application.py @@ -33,6 +33,7 @@ import craft_cli import craft_parts +import craft_platforms import craft_providers import pydantic import pytest @@ -60,12 +61,11 @@ get_other_command_group, ) from craft_application.models import BuildInfo -from craft_application.util import ( - get_host_architecture, # pyright: ignore[reportGeneralTypeIssues] -) from tests.conftest import FakeApplication from tests.unit.conftest import BASIC_PROJECT_YAML +HOST_ARCH = craft_platforms.DebianArchitecture.from_host().value + EMPTY_COMMAND_GROUP = craft_cli.CommandGroup("FakeCommands", []) FULL_PROJECT_YAML = """ @@ -505,9 +505,7 @@ def test_merge_default_commands_only(app): [(True, pathlib.PurePosixPath("/tmp/testcraft.log")), (False, None)], ) def test_log_path(monkeypatch, app, provider_managed, expected): - monkeypatch.setattr( - app.services.get_class("provider"), "is_managed", lambda: provider_managed - ) + monkeypatch.setattr(util, "is_managed_mode", lambda: provider_managed) actual = app.log_path @@ -520,9 +518,8 @@ def test_run_managed_success(mocker, app, fake_project, fake_build_plan): app.project = fake_project app._build_plan = fake_build_plan mock_pause = mocker.spy(craft_cli.emit, "pause") - arch = get_host_architecture() - app.run_managed(None, arch) + app.run_managed(None, HOST_ARCH) assert ( mock.call( @@ -544,7 +541,7 @@ def test_run_managed_failure(app, fake_project, fake_build_plan): app._build_plan = fake_build_plan with pytest.raises(craft_providers.ProviderError) as exc_info: - app.run_managed(None, get_host_architecture()) + app.run_managed(None, HOST_ARCH) assert exc_info.value.brief == "Failed to execute testcraft in instance." @@ -584,7 +581,7 @@ def test_run_managed_secrets( secret_strings=set(), ) - app.run_managed(None, get_host_architecture()) + app.run_managed(None, HOST_ARCH) # Check that the encoded secrets were propagated to the managed instance. assert len(mock_execute.mock_calls) == 1 @@ -604,7 +601,7 @@ def test_run_managed_multiple(app, fake_project): app.services.provider = mock_provider app.set_project(fake_project) - arch = get_host_architecture() + arch = HOST_ARCH info1 = BuildInfo("a1", arch, "arch1", bases.BaseName("base", "1")) info2 = BuildInfo("a2", arch, "arch2", bases.BaseName("base", "2")) app._build_plan = [info1, info2] @@ -624,7 +621,7 @@ def test_run_managed_specified_arch(app, fake_project): app.services.provider = mock_provider app.set_project(fake_project) - arch = get_host_architecture() + arch = HOST_ARCH info1 = BuildInfo("a1", arch, "arch1", bases.BaseName("base", "1")) info2 = BuildInfo("a2", arch, "arch2", bases.BaseName("base", "2")) app._build_plan = [info1, info2] @@ -644,7 +641,7 @@ def test_run_managed_specified_platform(app, fake_project): app.services.provider = mock_provider app.set_project(fake_project) - arch = get_host_architecture() + arch = HOST_ARCH info1 = BuildInfo("a1", arch, "arch1", bases.BaseName("base", "1")) info2 = BuildInfo("a2", arch, "arch2", bases.BaseName("base", "2")) app._build_plan = [info1, info2] @@ -717,9 +714,7 @@ def test_get_arg_or_config(monkeypatch, app, parsed_args, environ, item, expecte def test_get_dispatcher_error( monkeypatch, check, capsys, app, mock_dispatcher, managed, error, exit_code, message ): - monkeypatch.setattr( - app.services.get_class("provider"), "is_managed", lambda: managed - ) + monkeypatch.setattr(util, "is_managed_mode", lambda: managed) mock_dispatcher.pre_parse_args.side_effect = error with pytest.raises(SystemExit) as exc_info: @@ -838,8 +833,8 @@ def test_set_verbosity_from_env_incorrect(monkeypatch, capsys, app): assert craft_cli.emit._mode == craft_cli.EmitterMode.BRIEF -def test_pre_run_project_dir_managed(app): - app.is_managed = lambda: True +def test_pre_run_project_dir_managed(monkeypatch, app): + monkeypatch.setattr(util, "is_managed_mode", lambda: True) dispatcher = mock.Mock(spec_set=craft_cli.Dispatcher) app._pre_run(dispatcher) @@ -908,7 +903,7 @@ def test_run_success_managed(monkeypatch, app, fake_project, mocker): def test_run_success_managed_with_arch(monkeypatch, app, fake_project, mocker): mocker.patch.object(app, "get_project", return_value=fake_project) app.run_managed = mock.Mock() - arch = get_host_architecture() + arch = HOST_ARCH monkeypatch.setattr(sys, "argv", ["testcraft", "pull", f"--build-for={arch}"]) pytest_check.equal(app.run(), 0) @@ -932,12 +927,12 @@ def test_run_success_managed_with_platform(monkeypatch, app, fake_project, mocke ([], mock.call(None, None)), (["--platform=s390x"], mock.call("s390x", None)), ( - ["--platform", get_host_architecture()], - mock.call(get_host_architecture(), None), + ["--platform", HOST_ARCH], + mock.call(HOST_ARCH, None), ), ( - ["--build-for", get_host_architecture()], - mock.call(None, get_host_architecture()), + ["--build-for", HOST_ARCH], + mock.call(None, HOST_ARCH), ), (["--build-for", "s390x"], mock.call(None, "s390x")), (["--platform", "s390x,riscv64"], mock.call("s390x", None)), @@ -1311,7 +1306,7 @@ def test_work_dir_project_non_managed(monkeypatch, app_metadata, fake_services): app = application.Application(app_metadata, fake_services) assert app._work_dir == pathlib.Path.cwd() - project = app.get_project(build_for=get_host_architecture()) + project = app.get_project(build_for=HOST_ARCH) # Make sure the project is loaded correctly (from the cwd) assert project is not None @@ -1326,7 +1321,7 @@ def test_work_dir_project_managed(monkeypatch, app_metadata, fake_services): app = application.Application(app_metadata, fake_services) assert app._work_dir == pathlib.PosixPath("/root") - project = app.get_project(build_for=get_host_architecture()) + project = app.get_project(build_for=HOST_ARCH) # Make sure the project is loaded correctly (from the cwd) assert project is not None @@ -1378,7 +1373,7 @@ def test_expand_environment_build_for_all( base: ubuntu@24.04 platforms: platform1: - build-on: [{util.get_host_architecture()}] + build-on: [{HOST_ARCH}] build-for: [all] parts: mypart: @@ -1397,12 +1392,12 @@ def test_expand_environment_build_for_all( # Make sure the project is loaded correctly (from the cwd) assert project is not None assert project.parts["mypart"]["build-environment"] == [ - {"BUILD_ON": util.get_host_architecture()}, - {"BUILD_FOR": util.get_host_architecture()}, + {"BUILD_ON": HOST_ARCH}, + {"BUILD_FOR": HOST_ARCH}, ] emitter.assert_debug( "Expanding environment variables with the host architecture " - f"{util.get_host_architecture()!r} as the build-for architecture " + f"{HOST_ARCH!r} as the build-for architecture " "because 'all' was specified." ) @@ -1410,14 +1405,14 @@ def test_expand_environment_build_for_all( @pytest.mark.usefixtures("environment_project") def test_application_expand_environment(app_metadata, fake_services): app = application.Application(app_metadata, fake_services) - project = app.get_project(build_for=get_host_architecture()) + project = app.get_project(build_for=HOST_ARCH) # Make sure the project is loaded correctly (from the cwd) assert project is not None assert project.parts["mypart"]["source-tag"] == "v1.2.3" assert project.parts["mypart"]["build-environment"] == [ - {"BUILD_ON": util.get_host_architecture()}, - {"BUILD_FOR": util.get_host_architecture()}, + {"BUILD_ON": HOST_ARCH}, + {"BUILD_FOR": HOST_ARCH}, ] @@ -1456,7 +1451,7 @@ def test_application_build_secrets(app_metadata, fake_services, monkeypatch, moc spied_set_secrets = mocker.spy(craft_cli.emit, "set_secrets") app = application.Application(app_metadata, fake_services) - project = app.get_project(build_for=get_host_architecture()) + project = app.get_project(build_for=HOST_ARCH) # Make sure the project is loaded correctly (from the cwd) assert project is not None @@ -1571,7 +1566,7 @@ def test_extra_yaml_transform(tmp_path, app_metadata, fake_services): app.project_dir = tmp_path _ = app.get_project(build_for="s390x") - assert app.build_on == util.get_host_architecture() + assert app.build_on == HOST_ARCH assert app.build_for == "s390x" @@ -1587,7 +1582,7 @@ def test_mandatory_adoptable_fields(tmp_path, app_metadata, fake_services): app.project_dir = tmp_path with pytest.raises(errors.CraftValidationError) as exc_info: - _ = app.get_project(build_for=get_host_architecture()) + _ = app.get_project(build_for=HOST_ARCH) assert ( str(exc_info.value) @@ -1748,18 +1743,18 @@ def test_process_grammar_to_all(tmp_path, app_metadata, fake_services): base: ubuntu@24.04 platforms: myplatform: - build-on: [{util.get_host_architecture()}] + build-on: [{HOST_ARCH}] build-for: [all] parts: mypart: plugin: nil build-packages: - test-package - - on {util.get_host_architecture()} to all: + - on {HOST_ARCH} to all: - on-host-to-all - to all: - to-all - - on {util.get_host_architecture()} to s390x: + - on {HOST_ARCH} to s390x: - on-host-to-s390x - to s390x: - on-amd64-to-s390x @@ -1869,7 +1864,7 @@ def test_process_yaml_from_extra_transform( ] assert project.parts["mypart"]["build-environment"] == [ # evaluate project variables - {"hello": get_host_architecture()}, + {"hello": HOST_ARCH}, # render secrets {"MY_VAR": "secret-value"}, ] @@ -1919,7 +1914,7 @@ def environment_partitions_project(monkeypatch, tmp_path): @pytest.mark.usefixtures("environment_partitions_project") def test_partition_application_expand_environment(app_metadata, fake_services): app = FakePartitionsApplication(app_metadata, fake_services) - project = app.get_project(build_for=get_host_architecture()) + project = app.get_project(build_for=HOST_ARCH) assert craft_parts.Features().enable_partitions is True # Make sure the project is loaded correctly (from the cwd) @@ -2168,7 +2163,7 @@ def test_clean_platform(monkeypatch, tmp_path, app_metadata, fake_services, mock """Test that calling "clean --platform=x" correctly filters the build plan.""" data = util.safe_yaml_load(StringIO(BASIC_PROJECT_YAML)) # Put a few different platforms on the project - arch = util.get_host_architecture() + arch = HOST_ARCH build_on_for = { "build-on": [arch], "build-for": [arch], diff --git a/tests/unit/util/test_string.py b/tests/unit/util/test_string.py index 36953a1ea..8916cff4d 100644 --- a/tests/unit/util/test_string.py +++ b/tests/unit/util/test_string.py @@ -52,6 +52,8 @@ ("Off", False), ("0", False), ("0 ", False), + ("", False), + (" ", False), ], ) def test_strtobool(data, expected): @@ -66,8 +68,6 @@ def test_strtobool(data, expected): (None), ({}), ([]), - (""), - (" "), ("invalid"), ("2"), ("-"),