Source code for craft_parts.overlays.overlay_manager
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-## Copyright 2021 Canonical Ltd.## This program is free software; you can redistribute it and/or# modify it under the terms of the GNU Lesser General Public# License version 3 as published by the Free Software Foundation.## This program is distributed in the hope that it will be useful,# but WITHOUT ANY WARRANTY; without even the implied warranty of# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU# Lesser General Public License for more details.## You should have received a copy of the GNU Lesser General Public License# along with this program. If not, see <http://www.gnu.org/licenses/>."""Overlay mount operations and package installation helpers."""importloggingimportosimportsysfrompathlibimportPathfromtypingimportLiteralfromcraft_partsimportpackagesfromcraft_parts.infosimportProjectInfofromcraft_parts.partsimportPartfrom.importchrootfrom.overlay_fsimportOverlayFSlogger=logging.getLogger(__name__)
[docs]classOverlayManager:"""Execution time overlay mounting and package installation. :param project_info: The project information. :param part_list: A list of all parts in the project. :param base_layer_dir: The directory containing the overlay base, or None if the project doesn't use overlay parameters. """def__init__(self,*,project_info:ProjectInfo,part_list:list[Part],base_layer_dir:Path|None,)->None:self._project_info=project_infoself._part_list=part_listself._layer_dirs=[p.part_layer_dirforpinpart_list]self._overlay_fs:OverlayFS|None=Noneself._base_layer_dir=base_layer_dir@propertydefbase_layer_dir(self)->Path|None:"""Return the path to the base layer, if any."""returnself._base_layer_dir
[docs]defmount_layer(self,part:Part,*,pkg_cache:bool=False)->None:"""Mount the overlay step layer stack up to the given part. :param part: The part corresponding to the topmost layer to mount. :param pkg cache: Whether the package cache layer is enabled. """ifnotself._base_layer_dir:raiseRuntimeError("request to mount overlay without a base layer")lowers=[self._base_layer_dir]ifpkg_cache:lowers.append(self._project_info.overlay_packages_dir)index=self._part_list.index(part)lowers.extend(self._layer_dirs[0:index])upper=self._layer_dirs[index]# lower dirs are stacked from right to leftlowers.reverse()self._overlay_fs=OverlayFS(lower_dirs=lowers,upper_dir=upper,work_dir=self._project_info.overlay_work_dir,)self._overlay_fs.mount(self._project_info.overlay_mount_dir)
[docs]defmount_pkg_cache(self)->None:"""Mount the overlay step package cache layer."""ifnotself._base_layer_dir:raiseRuntimeError("request to mount the overlay package cache without a base layer")self._overlay_fs=OverlayFS(lower_dirs=[self._base_layer_dir],upper_dir=self._project_info.overlay_packages_dir,work_dir=self._project_info.overlay_work_dir,)self._overlay_fs.mount(self._project_info.overlay_mount_dir)
[docs]defunmount(self)->None:"""Unmount the overlay step layer stack."""ifnotself._overlay_fs:raiseRuntimeError("filesystem is not mounted")self._overlay_fs.unmount()self._overlay_fs=None
[docs]defmkdirs(self)->None:"""Create overlay directories and mountpoints."""foroverlay_dirin[self._project_info.overlay_mount_dir,self._project_info.overlay_packages_dir,self._project_info.overlay_work_dir,]:overlay_dir.mkdir(parents=True,exist_ok=True)
[docs]defrefresh_packages_list(self)->None:"""Update the list of available packages in the overlay system."""ifnotself._overlay_fs:raiseRuntimeError("overlay filesystem not mounted")mount_dir=self._project_info.overlay_mount_dir# Ensure we always run refresh_packages_list by resetting the cachepackages.Repository.refresh_packages_list.cache_clear()# type: ignore[attr-defined]chroot.chroot(mount_dir,packages.Repository.refresh_packages_list)
[docs]defdownload_packages(self,package_names:list[str])->None:"""Download packages and populate the overlay package cache. :param package_names: The list of packages to download. """ifnotself._overlay_fs:raiseRuntimeError("overlay filesystem not mounted")mount_dir=self._project_info.overlay_mount_dirchroot.chroot(mount_dir,packages.Repository.download_packages,package_names)
[docs]definstall_packages(self,package_names:list[str])->None:"""Install packages on the overlay area using chroot. :param package_names: The list of packages to install. """ifnotself._overlay_fs:raiseRuntimeError("overlay filesystem not mounted")mount_dir=self._project_info.overlay_mount_dirchroot.chroot(mount_dir,packages.Repository.install_packages,package_names,refresh_package_cache=False,)
[docs]classLayerMount:"""Mount the overlay layer stack for step processing. :param overlay_manager: The overlay manager. :param top_part: The topmost part to mount. :param pkg_cache: Whether to mount the overlay package cache. """def__init__(self,overlay_manager:OverlayManager,top_part:Part,pkg_cache:bool=True,# noqa: FBT001, FBT002)->None:self._overlay_manager=overlay_managerself._overlay_manager.mkdirs()self._top_part=top_partself._pkg_cache=pkg_cacheself._pid=os.getpid()def__enter__(self)->"LayerMount":self._overlay_manager.mount_layer(self._top_part,pkg_cache=self._pkg_cache,)returnselfdef__exit__(self,*exc:object)->Literal[False]:# prevent pychroot process leakifos.getpid()!=self._pid:sys.exit()self._overlay_manager.unmount()returnFalse
[docs]definstall_packages(self,package_names:list[str])->None:"""Install the specified packages on the local system. :param package_names: The list of packages to install. """self._overlay_manager.install_packages(package_names)
[docs]classPackageCacheMount:"""Mount and umount the overlay package cache. :param overlay_manager: The overlay manager. """def__init__(self,overlay_manager:OverlayManager)->None:self._overlay_manager=overlay_managerself._overlay_manager.mkdirs()self._pid=os.getpid()def__enter__(self)->"PackageCacheMount":self._overlay_manager.mount_pkg_cache()returnselfdef__exit__(self,*exc:object)->Literal[False]:# prevent pychroot process leakifos.getpid()!=self._pid:sys.exit()self._overlay_manager.unmount()returnFalse
[docs]defrefresh_packages_list(self)->None:"""Update the list of available packages in the overlay system."""self._overlay_manager.refresh_packages_list()
[docs]defdownload_packages(self,package_names:list[str])->None:"""Download the specified packages to the local system. :param package_names: The list of packages to download. """self._overlay_manager.download_packages(package_names)