diff --git a/snapcraft/application.py b/snapcraft/application.py index 85d3ef66d7..6c4cc400ce 100644 --- a/snapcraft/application.py +++ b/snapcraft/application.py @@ -84,10 +84,11 @@ def _get_esm_error_for_base(base: str) -> None: class Snapcraft(Application): """Snapcraft application definition.""" + _known_core24: bool + """True if the project should use the core24/craft-application codepath.""" + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - # Whether we know that we should use the core24-based codepath. - self._known_core24 = False self._parse_info: dict[str, list[str]] = {} # Locate the project file. It's used in early execution to determine @@ -98,13 +99,29 @@ def __init__(self, *args, **kwargs) -> None: self._snapcraft_yaml_path: pathlib.Path | None = self._resolve_project_path( None ) + with self._snapcraft_yaml_path.open() as file: + self._snapcraft_yaml_data = util.safe_yaml_load(file) except FileNotFoundError: - self._snapcraft_yaml_path = None + self._snapcraft_yaml_path = self._snapcraft_yaml_data = None + + self._known_core24 = self._get_known_core24() for craft_var, snapcraft_var in MAPPED_ENV_VARS.items(): if env_val := os.getenv(snapcraft_var): os.environ[craft_var] = env_val + def _get_known_core24(self) -> bool: + """Return true if the project is known to be core24.""" + if self._snapcraft_yaml_data: + base = self._snapcraft_yaml_data.get("base") + build_base = self._snapcraft_yaml_data.get("build-base") + + # We know for sure that we're handling a core24 project + if "core24" in (base, build_base) or build_base == "devel": + return True + + return False + def _get_app_plugins(self) -> dict[str, PluginType]: return plugins.get_plugins(core22=False) @@ -112,8 +129,10 @@ def _get_app_plugins(self) -> dict[str, PluginType]: def _register_default_plugins(self) -> None: """Register per application plugins when initializing.""" super()._register_default_plugins() - # dotnet is disabled for core24 because it is pending a rewrite - craft_parts.plugins.unregister("dotnet") + + if self._known_core24: + # dotnet is disabled for core24 and newer because it is pending a rewrite + craft_parts.plugins.unregister("dotnet") @override def _configure_services(self, provider_name: str | None) -> None: @@ -190,29 +209,28 @@ def _get_argv_command() -> str | None: @override def _get_dispatcher(self) -> craft_cli.Dispatcher: - # Handle "multiplexing" of Snapcraft "codebases" depending on the - # project's base (if any). Here, we handle the case where there *is* - # a project and it's core24, which means it should definitely fall into - # the craft-application-based flow. + """Handle multiplexing of Snapcraft "codebases" depending on the project's base. + + The ClassicFallback-based flow is used in any of the following scenarios: + - there is no project to load + - for core20 remote builds if SNAPCRAFT_REMOTE_BUILD_STRATEGY is not "disable-fallback" + - for core22 remote builds if SNAPCRAFT_REMOTE_BUILD_STRATEGY is "force-fallback" + + The craft-application-based flow is used in any of the following scenarios: + - the project base is core24 or newer + - for the "version" command + """ argv_command = self._get_argv_command() if argv_command == "lint": # We don't need to check for core24 if we're just linting return super()._get_dispatcher() - if self._snapcraft_yaml_path: - with self._snapcraft_yaml_path.open() as file: - yaml_data = util.safe_yaml_load(file) - base = yaml_data.get("base") - build_base = yaml_data.get("build-base") + if self._snapcraft_yaml_data: + base = self._snapcraft_yaml_data.get("base") + build_base = self._snapcraft_yaml_data.get("build-base") _get_esm_error_for_base(base) - if "core24" in (base, build_base) or build_base == "devel": - # We know for sure that we're handling a core24 project - self._known_core24 = True - elif ( - argv_command == "version" or "--version" in sys.argv or "-V" in sys.argv - ): - pass - elif argv_command == "remote-build" and any( + + if argv_command == "remote-build" and any( b in ("core20", "core22") for b in (base, build_base) ): build_strategy = os.environ.get("SNAPCRAFT_REMOTE_BUILD_STRATEGY", None) @@ -239,7 +257,9 @@ def _get_dispatcher(self) -> craft_cli.Dispatcher: and build_strategy == "force-fallback" ): raise errors.ClassicFallback() - else: + elif not self._known_core24 and not ( + argv_command == "version" or "--version" in sys.argv or "-V" in sys.argv + ): raise errors.ClassicFallback() return super()._get_dispatcher() diff --git a/tests/spread/core24/dotnet/snapcraft.yaml b/tests/spread/core24/dotnet/snapcraft.yaml new file mode 100644 index 0000000000..7ab22ace9b --- /dev/null +++ b/tests/spread/core24/dotnet/snapcraft.yaml @@ -0,0 +1,18 @@ +name: dotnet-hello +base: core24 +version: '1.0' +summary: a simple dotnet snap +description: a simple dotnet snap + +confinement: strict + +apps: + dotnet-hello: + command: dotnet + +parts: + hello: + plugin: dotnet + source: . + dotnet-self-contained-runtime-identifier: linux-x64 + build-snaps: [dotnet-sdk] diff --git a/tests/spread/core24/dotnet/task.yaml b/tests/spread/core24/dotnet/task.yaml new file mode 100644 index 0000000000..b29b8a669c --- /dev/null +++ b/tests/spread/core24/dotnet/task.yaml @@ -0,0 +1,11 @@ +summary: Build a snap with the dotnet plugin + +restore: | + rm -rf ./*.snap + +execute: | + # Unset SNAPCRAFT_BUILD_ENVIRONMENT=host. + unset SNAPCRAFT_BUILD_ENVIRONMENT + + # dotnet is disabled for core24 until the dotnet sdk snap is rewritten + snapcraft pack 2>&1 | MATCH "plugin not registered: 'dotnet'" diff --git a/tests/spread/plugins/craft-parts/build-and-run-hello/dotnet-hello/dotnet.csproj b/tests/spread/plugins/craft-parts/build-and-run-hello/dotnet-hello/dotnet.csproj new file mode 100644 index 0000000000..38e52b4c02 --- /dev/null +++ b/tests/spread/plugins/craft-parts/build-and-run-hello/dotnet-hello/dotnet.csproj @@ -0,0 +1,10 @@ + + + Exe + net6.0 + enable + enable + linux-x64 + true + + diff --git a/tests/spread/plugins/craft-parts/build-and-run-hello/dotnet-hello/hello.cs b/tests/spread/plugins/craft-parts/build-and-run-hello/dotnet-hello/hello.cs new file mode 100644 index 0000000000..f4a93f7f19 --- /dev/null +++ b/tests/spread/plugins/craft-parts/build-and-run-hello/dotnet-hello/hello.cs @@ -0,0 +1 @@ +Console.WriteLine("hello world"); diff --git a/tests/spread/plugins/craft-parts/build-and-run-hello/dotnet-hello/snap/snapcraft.yaml b/tests/spread/plugins/craft-parts/build-and-run-hello/dotnet-hello/snap/snapcraft.yaml new file mode 100644 index 0000000000..ff8345e692 --- /dev/null +++ b/tests/spread/plugins/craft-parts/build-and-run-hello/dotnet-hello/snap/snapcraft.yaml @@ -0,0 +1,18 @@ +name: dotnet-hello +base: core22 +version: '1.0' +summary: a simple dotnet snap +description: a simple dotnet snap + +confinement: strict + +apps: + dotnet-hello: + command: dotnet + +parts: + hello: + plugin: dotnet + source: . + dotnet-self-contained-runtime-identifier: linux-x64 + build-snaps: [dotnet-sdk] diff --git a/tests/spread/plugins/craft-parts/build-and-run-hello/task.yaml b/tests/spread/plugins/craft-parts/build-and-run-hello/task.yaml index 6c083adb31..bbb9bbea02 100644 --- a/tests/spread/plugins/craft-parts/build-and-run-hello/task.yaml +++ b/tests/spread/plugins/craft-parts/build-and-run-hello/task.yaml @@ -10,6 +10,7 @@ environment: SNAP/python: python-hello SNAP/qmake: qmake-hello SNAP/maven: maven-hello + SNAP/dotnet: dotnet-hello prepare: | #shellcheck source=tests/spread/tools/snapcraft-yaml.sh @@ -28,6 +29,7 @@ restore: | # Undo changes to hello [ -f hello ] && git checkout hello [ -f hello.c ] && git checkout hello.c + [ -f hello.cs ] && git checkout hello.cs [ -f subdir/hello.c ] && git checkout subdir/hello.c [ -f hello.js ] && git checkout hello.js [ -f main.go ] && git checkout main.go @@ -72,6 +74,8 @@ execute: | modified_file=hello elif [ -f hello.c ]; then modified_file=hello.c + elif [ -f hello.cs ]; then + modified_file=hello.cs elif [ -f subdir/hello.c ]; then modified_file=subdir/hello.c elif [ -f hello.js ]; then diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 129e7f92b9..3708e5aa3c 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -23,7 +23,7 @@ import pytest import yaml -from craft_parts import Features, callbacks +from craft_parts import Features, callbacks, plugins from craft_providers import Executor, Provider from craft_providers.base import Base from overrides import override @@ -37,6 +37,13 @@ def unregister_callbacks(mocker): callbacks.unregister_all() +@pytest.fixture(autouse=True) +def reset_plugins(): + # craft-part modifies a dictionary of plugins that doesn't get reloaded between tests + # 'unregister_all()' resets to the dictionary to the default value + plugins.unregister_all() + + @pytest.fixture def snapcraft_yaml(new_dir): """Return a fixture that can write a snapcraft.yaml.""" diff --git a/tests/unit/test_application.py b/tests/unit/test_application.py index 47c4900352..bbeb46e22b 100644 --- a/tests/unit/test_application.py +++ b/tests/unit/test_application.py @@ -20,6 +20,7 @@ from textwrap import dedent import craft_cli +import craft_parts.plugins import pytest import yaml from craft_application import util @@ -308,13 +309,45 @@ def test_application_plugins(): assert "kernel" not in plugins -def test_application_dotnet_not_registered(): - """dotnet plugin is disable for core24.""" +@pytest.mark.parametrize( + ("base", "build_base"), + [ + ("core20", None), + ("core20", "core20"), + ("core20", "devel"), + ("core22", None), + ("core22", "core22"), + ("core22", "devel"), + ], +) +def test_application_dotnet_registered(base, build_base, snapcraft_yaml): + """dotnet plugin is enabled for core22.""" + snapcraft_yaml(base=base, build_base=build_base) app = application.create_app() - plugins = app._get_app_plugins() + app._register_default_plugins() + + assert "dotnet" in craft_parts.plugins.get_registered_plugins() + + +@pytest.mark.parametrize( + ("base", "build_base"), + [ + ("core24", None), + ("core24", "core20"), + ("core24", "core22"), + ("core24", "core24"), + ("core24", "devel"), + ], +) +def test_application_dotnet_not_registered(base, build_base, snapcraft_yaml): + """dotnet plugin is disabled for core24 and newer bases.""" + snapcraft_yaml(base=base, build_base=build_base) + app = application.create_app() - assert "dotnet" not in plugins + app._register_default_plugins() + + assert "dotnet" not in craft_parts.plugins.get_registered_plugins() def test_default_command_integrated(monkeypatch, mocker, new_dir): @@ -483,3 +516,26 @@ def test_run_envvar_invalid(snapcraft_yaml, base, monkeypatch): "'SNAPCRAFT_REMOTE_BUILD_STRATEGY'. Valid values are 'disable-fallback' and " "'force-fallback'" ) + + +@pytest.mark.parametrize( + ("base", "build_base", "is_known_core24"), + [ + ("core20", None, False), + ("core20", "core20", False), + ("core20", "devel", False), + ("core22", None, False), + ("core22", "core22", False), + ("core22", "devel", False), + ("core24", "core22", True), + ("core24", None, True), + ("core24", "core24", True), + ("core24", "devel", True), + ], +) +def test_known_core24(snapcraft_yaml, base, build_base, is_known_core24): + snapcraft_yaml(base=base, build_base=build_base) + + app = application.create_app() + + assert app._known_core24 == is_known_core24