Skip to content

Commit

Permalink
Merge branch 'main' into work/CRAFT-3206-manifest-models
Browse files Browse the repository at this point in the history
  • Loading branch information
tigarmo authored Sep 19, 2024
2 parents f64f069 + 616b3f0 commit 81ca81a
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 48 deletions.
5 changes: 4 additions & 1 deletion craft_application/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,10 @@ def get_arg_or_config(
:param item: the name of the namespace or config item.
:returns: the requested value.
"""
return getattr(parsed_args, item, self.services.config.get(item))
arg_value = getattr(parsed_args, item, None)
if arg_value is not None:
return arg_value
return self.services.config.get(item)

def _run_inner(self) -> int:
"""Actual run implementation."""
Expand Down
3 changes: 3 additions & 0 deletions craft_application/fetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ def start_service() -> subprocess.Popen[str] | None:
# Accept permissive sessions
cmd.append("--permissive-mode")

# Shutdown after 5 minutes with no live sessions
cmd.append("--idle-shutdown=300")

log_filepath = _get_log_filepath()
log_filepath.parent.mkdir(parents=True, exist_ok=True)

Expand Down
16 changes: 10 additions & 6 deletions craft_application/services/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,16 @@ def setup(self) -> None:
self.environment[f"{scheme.upper()}_PROXY"] = value

if self._install_snap:
channel = (
None
if util.is_running_from_snap(self._app.name)
else os.getenv("CRAFT_SNAP_CHANNEL", "latest/stable")
)
self.snaps.append(Snap(name=self._app.name, channel=channel, classic=True))
if util.is_running_from_snap(self._app.name):
# use the aliased name of the snap when injecting
name = os.getenv("SNAP_INSTANCE_NAME", self._app.name)
channel = None
else:
# use the snap name when installing from the store
name = self._app.name
channel = os.getenv("CRAFT_SNAP_CHANNEL", "latest/stable")

self.snaps.append(Snap(name=name, channel=channel, classic=True))

@contextlib.contextmanager
def instance(
Expand Down
60 changes: 36 additions & 24 deletions docs/howto/partitions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,45 +45,57 @@ Required application changes

To add partition support to an application, two basic changes are needed:

#. Enable the feature
#. Enable the feature.

Use the :class:`Features <craft_parts.Features>` class to specify that the
application will use partitions:
In your Application subclass, override the following method and invoke the
:class:`Features <craft_parts.Features>` class:

.. code-block:: python
from craft_parts import Features
Features.reset()
Features(enable_partitions=True)
class ExampleApplication(Application):
.. NOTE::
The ``craft-application`` class :class:`AppFeatures
<craft_application.AppFeatures>` has a similar name and serves a similar
purpose to ``craft-parts``'s :class:`Features <craft_parts.Features>`,
but partitions cannot be enabled via :class:`AppFeatures
<craft_application.AppFeatures>`!
...
#. Define the list of partitions
@override
def _enable_craft_parts_features(self) -> None:
Features(enable_partitions=True)
We need to tell the :class:`LifecycleManager <craft_parts.LifecycleManager>`
class about our partitions, but applications do not usually directly
instantiate the LifecycleManager.
You can only be enable partitions with the :class:`Features
<craft_parts.Features>` class from craft-parts. In craft-application
there's a similarly-named :class:`AppFeatures
<craft_application.AppFeatures>` class which serves a similar purpose,
but it can't enable partitions.

Instead, override your :class:`Application
<craft_application.Application>`'s ``_setup_partitions`` method, and return
a list of the partitions, which will eventually be passed to the
:class:`LifecycleManager <craft_parts.LifecycleManager>`:
.. Tip::
In unit tests, the :class:`Features <craft_parts.Features>` global
singleton may raise exceptions when successive tests repeatedly try to
enable partitions.

To prevent these errors, reset the features at the start of each test:

.. code-block:: python
Features.reset()
#. Define the list of partitions.

Override the ``_setup_partitions`` method of your :class:`Application
<craft_application.Application>` class and return the list of the
partitions.

.. code-block:: python
class SnackcraftApplication(Application):
class ExampleApplication(Application):
...
...
@override
def _setup_partitions(self, yaml_data: dict[str, Any]) -> list[str] | None:
return ["default", "kernel", "component/bar-baz"]
@override
def _setup_partitions(self, yaml_data: dict[str, Any]) -> list[str] | None:
return ["default", "kernel", "component/bar-baz"]
Using the partitions
====================
Expand Down
12 changes: 12 additions & 0 deletions docs/reference/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@
Changelog
*********

4.2.3 (2024-Sep-18)
-------------------

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

- ``get_arg_or_config`` now correctly checks the config service if the passed
namespace has ``None`` as the value of the requested item.

For a complete list of commits, check out the `4.2.3`_ release on GitHub.

4.2.2 (2024-Sep-13)
-------------------

Expand Down Expand Up @@ -277,3 +288,4 @@ For a complete list of commits, check out the `2.7.0`_ release on GitHub.
.. _4.2.0: https://github.com/canonical/craft-application/releases/tag/4.2.0
.. _4.2.1: https://github.com/canonical/craft-application/releases/tag/4.2.1
.. _4.2.2: https://github.com/canonical/craft-application/releases/tag/4.2.2
.. _4.2.3: https://github.com/canonical/craft-application/releases/tag/4.2.3
64 changes: 48 additions & 16 deletions tests/unit/services/test_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,41 +96,70 @@ def test_setup_proxy_environment(


@pytest.mark.parametrize(
("install_snap", "environment", "snaps"),
("environment", "snaps"),
[
(True, {}, [Snap(name="testcraft", channel="latest/stable", classic=True)]),
(
True,
pytest.param(
{},
[Snap(name="testcraft", channel="latest/stable", classic=True)],
id="install-from-store-default-channel",
),
pytest.param(
{"CRAFT_SNAP_CHANNEL": "something"},
[Snap(name="testcraft", channel="something", classic=True)],
id="install-from-store-with-channel",
),
(
True,
{"SNAP_NAME": "testcraft", "SNAP": "/snap/testcraft/x1"},
[Snap(name="testcraft", channel=None, classic=True)],
pytest.param(
{
"SNAP_NAME": "testcraft",
"SNAP_INSTANCE_NAME": "testcraft_1",
"SNAP": "/snap/testcraft/x1",
},
[Snap(name="testcraft_1", channel=None, classic=True)],
id="inject-from-host",
),
(
True,
pytest.param(
{
"SNAP_NAME": "testcraft",
"SNAP_INSTANCE_NAME": "testcraft_1",
"SNAP": "/snap/testcraft/x1",
"CRAFT_SNAP_CHANNEL": "something",
},
[Snap(name="testcraft_1", channel=None, classic=True)],
id="inject-from-host-ignore-channel",
),
pytest.param(
# SNAP_INSTANCE_NAME may not exist if snapd < 2.43 or feature is disabled
{
"SNAP_NAME": "testcraft",
"SNAP": "/snap/testcraft/x1",
},
[Snap(name="testcraft", channel=None, classic=True)],
id="missing-snap-instance-name",
),
(False, {}, []),
(False, {"CRAFT_SNAP_CHANNEL": "something"}, []),
(
False,
pytest.param(
# SNAP_INSTANCE_NAME may not exist if snapd < 2.43 or feature is disabled
{
"SNAP_NAME": "testcraft",
"SNAP": "/snap/testcraft/x1",
# CRAFT_SNAP_CHANNEL should be ignored
"CRAFT_SNAP_CHANNEL": "something",
},
[],
[Snap(name="testcraft", channel=None, classic=True)],
id="missing-snap-instance-name-ignore-snap-channel",
),
pytest.param(
# this can happen when running testcraft from a venv in a snapped terminal
{
"SNAP_NAME": "kitty",
"SNAP_INSTANCE_NAME": "kitty",
"SNAP": "/snap/kitty/x1",
},
[Snap(name="testcraft", channel="latest/stable", classic=True)],
id="running-inside-another-snap",
),
],
)
@pytest.mark.parametrize("install_snap", [True, False])
def test_install_snap(
monkeypatch,
app_metadata,
Expand All @@ -155,7 +184,10 @@ def test_install_snap(
)
service.setup()

assert service.snaps == snaps
if install_snap:
assert service.snaps == snaps
else:
assert service.snaps == []


@pytest.mark.parametrize(
Expand Down
32 changes: 32 additions & 0 deletions tests/unit/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,38 @@ def test_run_managed_empty_plan(app, fake_project):
app.run_managed(None, None)


@pytest.mark.parametrize(
("parsed_args", "environ", "item", "expected"),
[
(argparse.Namespace(), {}, "build_for", None),
(argparse.Namespace(build_for=None), {}, "build_for", None),
(
argparse.Namespace(build_for=None),
{"CRAFT_BUILD_FOR": "arm64"},
"build_for",
"arm64",
),
(
argparse.Namespace(build_for=None),
{"TESTCRAFT_BUILD_FOR": "arm64"},
"build_for",
"arm64",
),
(
argparse.Namespace(build_for="riscv64"),
{"TESTCRAFT_BUILD_FOR": "arm64"},
"build_for",
"riscv64",
),
],
)
def test_get_arg_or_config(monkeypatch, app, parsed_args, environ, item, expected):
for var, content in environ.items():
monkeypatch.setenv(var, content)

assert app.get_arg_or_config(parsed_args, item) == expected


@pytest.mark.parametrize(
("managed", "error", "exit_code", "message"),
[
Expand Down
1 change: 1 addition & 0 deletions tests/unit/test_fetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ def test_start_service(mocker, tmp_path):
f"--cert={fake_cert}",
f"--key={fake_key}",
"--permissive-mode",
"--idle-shutdown=300",
]
)
+ f" > {fetch._get_log_filepath()}",
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ commands = sphinx-build {posargs:-b html} -W {tox_root}/docs {tox_root}/docs/_bu
[testenv:autobuild-docs]
description = Build documentation with an autoupdating server
base = docs
commands = sphinx-autobuild {posargs:-b html --open-browser --port 8080} -W --watch {tox_root}/craft_application {tox_root}/docs {tox_root}/docs/_build/html
commands = sphinx-autobuild {posargs:-b html --open-browser --port 8080} -W --watch {tox_root}/craft_application {tox_root}/docs {tox_root}/docs/_build

[testenv:lint-docs]
description = Lint the documentation with sphinx-lint
Expand Down

0 comments on commit 81ca81a

Please sign in to comment.