Skip to content

Commit

Permalink
Merge branch 'canonical:main' into sgmoore/add_qmake_plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
ScarlettGatelyMoore authored May 30, 2023
2 parents 3bd056f + a371084 commit 9af7da5
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 6 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install python packages and dependencies
Expand Down Expand Up @@ -60,11 +60,11 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install apt build dependencies
Expand Down
34 changes: 33 additions & 1 deletion craft_parts/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import itertools
import logging
from collections import namedtuple
from pathlib import Path
from typing import Callable, Iterable, List, Optional, Set, Union

from craft_parts import errors
Expand All @@ -30,9 +31,13 @@
FilterCallback = Callable[[ProjectInfo], Iterable[str]]
ExecutionCallback = Callable[[ProjectInfo], None]
StepCallback = Callable[[StepInfo], bool]
Callback = Union[FilterCallback, ExecutionCallback, StepCallback]
ConfigureOverlayCallback = Callable[[Path, ProjectInfo], None]
Callback = Union[
FilterCallback, ExecutionCallback, StepCallback, ConfigureOverlayCallback
]

_STAGE_PACKAGE_FILTERS: List[CallbackHook] = []
_OVERLAY_HOOKS: List[CallbackHook] = []
_PROLOGUE_HOOKS: List[CallbackHook] = []
_EPILOGUE_HOOKS: List[CallbackHook] = []
_PRE_STEP_HOOKS: List[CallbackHook] = []
Expand All @@ -54,6 +59,22 @@ def register_stage_packages_filter(func: FilterCallback) -> None:
_STAGE_PACKAGE_FILTERS.append(CallbackHook(func, None))


def register_configure_overlay(func: ConfigureOverlayCallback) -> None:
"""Register a callback function to configure the mounted overlay.
This "hook" is called after the overlay's package cache layer is mounted, but
*before* the package list is refreshed. It can be used to configure the
overlay's system, typically to install extra package repositories for Apt.
Note that when the hook is called the overlay is mounted but *not* chroot'ed
into.
:param func: The callback function that will be called with the location of
the overlay mount and the project info.
"""
_ensure_not_defined(func, _OVERLAY_HOOKS)
_OVERLAY_HOOKS.append(CallbackHook(func, None))


def register_prologue(func: ExecutionCallback) -> None:
"""Register an execution prologue callback function.
Expand Down Expand Up @@ -101,6 +122,7 @@ def register_post_step(
def unregister_all() -> None:
"""Clear all existing registered callback functions."""
_STAGE_PACKAGE_FILTERS[:] = []
_OVERLAY_HOOKS[:] = []
_PROLOGUE_HOOKS[:] = []
_EPILOGUE_HOOKS[:] = []
_PRE_STEP_HOOKS[:] = []
Expand All @@ -122,6 +144,16 @@ def get_stage_packages_filters(project_info: ProjectInfo) -> Optional[Set[str]]:
)


def run_configure_overlay(overlay_dir: Path, project_info: ProjectInfo) -> None:
"""Run all registered 'configure overlay' callbacks.
:param overlay_dir: The location where the overlay is mounted.
:param project_info: The project information to be sent to callback functions.
"""
for hook in _OVERLAY_HOOKS:
hook.function(overlay_dir, project_info)


def run_prologue(project_info: ProjectInfo) -> None:
"""Run all registered execution prologue callbacks.
Expand Down
3 changes: 3 additions & 0 deletions craft_parts/executor/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ def prologue(self) -> None:
# overlay packages.
if any(p.spec.overlay_packages for p in self._part_list):
with overlays.PackageCacheMount(self._overlay_manager) as ctx:
callbacks.run_configure_overlay(
self._project_info.overlay_mount_dir, self._project_info
)
ctx.refresh_packages_list()

callbacks.run_prologue(self._project_info)
Expand Down
5 changes: 5 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ Changelog
- Tox and packaging updates
- Documentation updates

1.19.5 (2023-05-23)
-------------------

- Revert pyproject.toml change (breaks semantic versioning)

1.19.4 (2023-05-19)
-------------------

Expand Down
4 changes: 2 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,9 @@ def fake_snapd():
socket_path_patcher = mock.patch(
"craft_parts.packages.snaps.get_snapd_socket_path_template"
)
escaped_path = snapd_fake_socket_path.replace("/", "%2F")
mock_socket_path = socket_path_patcher.start()
mock_socket_path.return_value = f'\
http+unix://{snapd_fake_socket_path.replace("/", "%2F")}/v2/{{}}'
mock_socket_path.return_value = f"http+unix://{escaped_path}/v2/{{}}"

thread = server.start_fake_server(snapd_fake_socket_path)

Expand Down
34 changes: 34 additions & 0 deletions tests/unit/executor/test_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,40 @@ def test_prologue_overlay_packages(self, enable_overlay_feature, new_dir, mocker
with ExecutionContext(executor=e):
assert not mock_mount.called

def test_configure_overlay(self, enable_overlay_feature, new_dir, mocker):
"""Check that the configure_overlay callback is called when mounting the overlay's package cache."""

mocker.patch.object(overlays.OverlayManager, "mount_pkg_cache")
mocker.patch.object(overlays.OverlayManager, "unmount")

# This list will contain a record of the calls that are made, in order.
call_order = []

def configure_overlay(overlay_dir: Path, project_info: ProjectInfo) -> None:
call_order.append(f"configure_overlay: {overlay_dir} {project_info.custom}")

def refresh_packages_list() -> None:
call_order.append("refresh_packages_list")

callbacks.register_configure_overlay(configure_overlay)
mocker.patch.object(
overlays.PackageCacheMount,
"refresh_packages_list",
side_effect=refresh_packages_list,
)

p1 = Part("p1", {"plugin": "nil", "overlay-packages": ["fake-pkg"]})
info = ProjectInfo(application_name="test", cache_dir=new_dir, custom="custom")
e = Executor(project_info=info, part_list=[p1])

with ExecutionContext(executor=e):
# The `configure_overlay()` callback must've been called _before_
# refresh_packages_list().
assert call_order == [
f"configure_overlay: {info.overlay_mount_dir} custom",
"refresh_packages_list",
]

def test_capture_stdout(self, capfd, new_dir):
def cbf(info):
print(f"prologue {info.custom}")
Expand Down
38 changes: 38 additions & 0 deletions tests/unit/test_callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ def _callback_filter_2(info: ProjectInfo) -> Generator[str, None, None]:
return (i for i in ["d", "e", "f"])


def _callback_overlay_1(overlay_dir: Path, info: ProjectInfo) -> None:
greet = getattr(info, "greet")
print(f"{overlay_dir} {greet} 1")


def _callback_overlay_2(overlay_dir: Path, info: ProjectInfo) -> None:
greet = getattr(info, "greet")
print(f"{overlay_dir} {greet} 2")


class TestCallbackRegistration:
"""Test different scenarios of callback function registration."""

Expand Down Expand Up @@ -133,6 +143,19 @@ def test_register_stage_packages_filters(self):
# But we can register a different one
callbacks.register_stage_packages_filter(_callback_filter_2)

def test_register_configure_overlay(self):
callbacks.register_configure_overlay(_callback_overlay_1)

# A callback function shouldn't be registered again
with pytest.raises(errors.CallbackRegistrationError) as raised:
callbacks.register_configure_overlay(_callback_overlay_1)
assert raised.value.message == (
"callback function '_callback_overlay_1' is already registered."
)

# But we can register a different one
callbacks.register_configure_overlay(_callback_overlay_2)

def test_register_both_pre_and_post(self):
callbacks.register_pre_step(_callback_1)
callbacks.register_post_step(_callback_1)
Expand All @@ -144,13 +167,17 @@ def test_register_both_prologue_and_epilogue(self):
def test_unregister_all(self):
callbacks.register_stage_packages_filter(_callback_filter_1)
callbacks.register_stage_packages_filter(_callback_filter_2)
callbacks.register_configure_overlay(_callback_overlay_1)
callbacks.register_configure_overlay(_callback_overlay_2)
callbacks.register_pre_step(_callback_1)
callbacks.register_post_step(_callback_1)
callbacks.register_prologue(_callback_3)
callbacks.register_epilogue(_callback_3)
callbacks.unregister_all()
callbacks.register_stage_packages_filter(_callback_filter_1)
callbacks.register_stage_packages_filter(_callback_filter_2)
callbacks.register_configure_overlay(_callback_overlay_1)
callbacks.register_configure_overlay(_callback_overlay_2)
callbacks.register_pre_step(_callback_1)
callbacks.register_post_step(_callback_1)
callbacks.register_prologue(_callback_3)
Expand Down Expand Up @@ -241,3 +268,14 @@ def test_filter_package_list(self, capfd, funcs, result, message):
out, err = capfd.readouterr()
assert not err
assert out == message

def test_configure_callback(self, capfd):
callbacks.register_configure_overlay(_callback_overlay_1)
callbacks.register_configure_overlay(_callback_overlay_2)

overlay_dir = Path("/overlay/mount")
callbacks.run_configure_overlay(overlay_dir, self._project_info)

out, err = capfd.readouterr()
assert not err
assert out == "/overlay/mount hello 1\n/overlay/mount hello 2\n"

0 comments on commit 9af7da5

Please sign in to comment.