mirror of
				https://github.com/arcan1s/ahriman.git
				synced 2025-10-30 21:33:43 +00:00 
			
		
		
		
	remove excess dependencies leaves
This commit is contained in:
		| @ -80,7 +80,7 @@ class Executor(PackageInfo, Cleaner): | ||||
|                     # clear changes and update commit hash | ||||
|                     self.reporter.package_changes_update(single.base, Changes(last_commit_sha)) | ||||
|                     # update dependencies list | ||||
|                     dependencies = PackageArchive(self.paths.build_directory, single).depends_on() | ||||
|                     dependencies = PackageArchive(self.paths.build_directory, single, self.pacman).depends_on() | ||||
|                     self.reporter.package_dependencies_update(single.base, dependencies) | ||||
|                     # update result set | ||||
|                     result.add_updated(single) | ||||
|  | ||||
| @ -57,6 +57,7 @@ class AURPackage: | ||||
|         provides(list[str]): list of packages which this package provides | ||||
|         license(list[str]): list of package licenses | ||||
|         keywords(list[str]): list of package keywords | ||||
|         groups(list[str]): list of package groups | ||||
|  | ||||
|     Examples: | ||||
|         Mainly this class must be used from class methods instead of default :func:`__init__()`:: | ||||
| @ -100,6 +101,7 @@ class AURPackage: | ||||
|     provides: list[str] = field(default_factory=list) | ||||
|     license: list[str] = field(default_factory=list) | ||||
|     keywords: list[str] = field(default_factory=list) | ||||
|     groups: list[str] = field(default_factory=list) | ||||
|  | ||||
|     @classmethod | ||||
|     def from_json(cls, dump: dict[str, Any]) -> Self: | ||||
| @ -153,6 +155,7 @@ class AURPackage: | ||||
|             provides=package.provides, | ||||
|             license=package.licenses, | ||||
|             keywords=[], | ||||
|             groups=package.groups, | ||||
|         ) | ||||
|  | ||||
|     @classmethod | ||||
| @ -191,6 +194,7 @@ class AURPackage: | ||||
|             provides=dump["provides"], | ||||
|             license=dump["licenses"], | ||||
|             keywords=[], | ||||
|             groups=dump["groups"], | ||||
|         ) | ||||
|  | ||||
|     @staticmethod | ||||
|  | ||||
| @ -34,6 +34,13 @@ class Dependencies: | ||||
|  | ||||
|     paths: dict[str, list[str]] = field(default_factory=dict) | ||||
|  | ||||
|     def __post_init__(self) -> None: | ||||
|         """ | ||||
|         remove empty paths | ||||
|         """ | ||||
|         paths = {path: packages for path, packages in self.paths.items() if packages} | ||||
|         object.__setattr__(self, "paths", paths) | ||||
|  | ||||
|     @classmethod | ||||
|     def from_json(cls, dump: dict[str, Any]) -> Self: | ||||
|         """ | ||||
|  | ||||
							
								
								
									
										52
									
								
								src/ahriman/models/filesystem_package.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/ahriman/models/filesystem_package.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | ||||
| # | ||||
| # Copyright (c) 2021-2024 ahriman team. | ||||
| # | ||||
| # This file is part of ahriman | ||||
| # (see https://github.com/arcan1s/ahriman). | ||||
| # | ||||
| # This program is free software: you can redistribute it and/or modify | ||||
| # it under the terms of the GNU General Public License as published by | ||||
| # the Free Software Foundation, either version 3 of the License, or | ||||
| # (at your option) any later version. | ||||
| # | ||||
| # 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 General Public License for more details. | ||||
| # | ||||
| # You should have received a copy of the GNU General Public License | ||||
| # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
| # | ||||
| from dataclasses import dataclass, field | ||||
| from pathlib import Path | ||||
|  | ||||
|  | ||||
| @dataclass(frozen=True, kw_only=True) | ||||
| class FilesystemPackage: | ||||
|     """ | ||||
|     class representing a simplified model for the package installed to filesystem | ||||
|  | ||||
|     Attributes: | ||||
|         base(str): package base name | ||||
|         dependencies(list[str]): list of package dependencies | ||||
|         directories(list[Path]): list of directories this package contains | ||||
|         files(list[Path]): list of files this package contains | ||||
|         groups(list[str]): list of groups of the package | ||||
|     """ | ||||
|  | ||||
|     base: str | ||||
|     groups: list[str] | ||||
|     dependencies: list[str] | ||||
|     directories: list[Path] = field(default_factory=list) | ||||
|     files: list[Path] = field(default_factory=list) | ||||
|  | ||||
|     @property | ||||
|     def is_valid(self) -> bool: | ||||
|         """ | ||||
|         quick check if this package must be used for the dependencies calculation. It checks that | ||||
|         1) package is not in the base group | ||||
|  | ||||
|         Returns: | ||||
|             bool: True in case if this package must be used for the dependencies calculation or False otherwise | ||||
|         """ | ||||
|         return "base" not in self.groups | ||||
| @ -23,8 +23,12 @@ from elftools.elf.elffile import ELFFile | ||||
| from pathlib import Path | ||||
| from typing import IO | ||||
|  | ||||
| from ahriman.core.alpm.pacman import Pacman | ||||
| from ahriman.core.alpm.remote import OfficialSyncdb | ||||
| from ahriman.core.exceptions import UnknownPackageError | ||||
| from ahriman.core.util import walk | ||||
| from ahriman.models.dependencies import Dependencies | ||||
| from ahriman.models.filesystem_package import FilesystemPackage | ||||
| from ahriman.models.package import Package | ||||
|  | ||||
|  | ||||
| @ -40,6 +44,7 @@ class PackageArchive: | ||||
|  | ||||
|     root: Path | ||||
|     package: Package | ||||
|     pacman: Pacman | ||||
|  | ||||
|     @staticmethod | ||||
|     def dynamic_needed(binary_path: Path) -> list[str]: | ||||
| @ -90,6 +95,27 @@ class PackageArchive: | ||||
|  | ||||
|         return magic_bytes == expected | ||||
|  | ||||
|     def _load_pacman_package(self, path: Path) -> FilesystemPackage: | ||||
|         """ | ||||
|         load pacman package model from path | ||||
|  | ||||
|         Args: | ||||
|             path(Path): path to package files database | ||||
|  | ||||
|         Returns: | ||||
|             FilesystemPackage: generated pacman package model with empty paths | ||||
|         """ | ||||
|         package_base, *_ = path.parent.name.rsplit("-", 2) | ||||
|         try: | ||||
|             pacman_package = OfficialSyncdb.info(package_base, pacman=self.pacman) | ||||
|             return FilesystemPackage( | ||||
|                 base=package_base, | ||||
|                 groups=pacman_package.groups, | ||||
|                 dependencies=pacman_package.depends, | ||||
|             ) | ||||
|         except UnknownPackageError: | ||||
|             return FilesystemPackage(base=package_base, groups=[], dependencies=[]) | ||||
|  | ||||
|     def depends_on(self) -> Dependencies: | ||||
|         """ | ||||
|         extract packages and paths which are required for this package | ||||
| @ -98,18 +124,28 @@ class PackageArchive: | ||||
|             Dependencies: map of the package name to set of paths used by this package | ||||
|         """ | ||||
|         dependencies, roots = self.depends_on_paths() | ||||
|         installed_packages = self.installed_packages() | ||||
|  | ||||
|         result: dict[str, list[str]] = {} | ||||
|         for package, (directories, files) in self.installed_packages().items(): | ||||
|             if package in self.package.packages: | ||||
|         result: dict[str, list[FilesystemPackage]] = {} | ||||
|         for package_base, package in installed_packages.items(): | ||||
|             if package_base in self.package.packages: | ||||
|                 continue  # skip package itself | ||||
|  | ||||
|             required_by = [directory for directory in directories if directory in roots] | ||||
|             required_by.extend(library for library in files if library.name in dependencies) | ||||
|             required_by = [directory for directory in package.directories if directory in roots] | ||||
|             required_by.extend(library for library in package.files if library.name in dependencies) | ||||
|  | ||||
|             for path in required_by: | ||||
|                 result.setdefault(str(path), []).append(package) | ||||
|  | ||||
|         # reduce trees | ||||
|         for path, packages in result.items(): | ||||
|             root_packages = [ | ||||
|                 package | ||||
|                 for package in packages | ||||
|                 if not any(package.base in other.dependencies for other in packages) | ||||
|             ] | ||||
|  | ||||
|  | ||||
|         return Dependencies(result) | ||||
|  | ||||
|     def depends_on_paths(self) -> tuple[set[str], set[Path]]: | ||||
| @ -130,7 +166,7 @@ class PackageArchive: | ||||
|  | ||||
|         return dependencies, roots | ||||
|  | ||||
|     def installed_packages(self) -> dict[str, tuple[list[Path], list[Path]]]: | ||||
|     def installed_packages(self) -> dict[str, FilesystemPackage]: | ||||
|         """ | ||||
|         extract list of the installed packages and their content | ||||
|  | ||||
| @ -142,9 +178,8 @@ class PackageArchive: | ||||
|  | ||||
|         pacman_local_files = self.root / "var" / "lib" / "pacman" / "local" | ||||
|         for path in filter(lambda fn: fn.name == "files", walk(pacman_local_files)): | ||||
|             package, *_ = path.parent.name.rsplit("-", 2) | ||||
|             package = self._load_pacman_package(path) | ||||
|  | ||||
|             directories, files = [], [] | ||||
|             is_files = False | ||||
|             for line in path.read_text(encoding="utf8").splitlines(): | ||||
|                 if not line:  # skip empty lines | ||||
| @ -156,10 +191,10 @@ class PackageArchive: | ||||
|  | ||||
|                 entry = Path(line) | ||||
|                 if line.endswith("/"):  # simple check if it is directory | ||||
|                     directories.append(entry) | ||||
|                     package.directories.append(entry) | ||||
|                 else: | ||||
|                     files.append(entry) | ||||
|                     package.files.append(entry) | ||||
|  | ||||
|             result[package] = directories, files | ||||
|             result[package.base] = package | ||||
|  | ||||
|         return result | ||||
|  | ||||
| @ -30,7 +30,7 @@ def test_package_logger_set_reset(database: SQLite) -> None: | ||||
|     database._package_logger_reset() | ||||
|     record = logging.makeLogRecord({}) | ||||
|     with pytest.raises(AttributeError): | ||||
|         record.package_id | ||||
|         assert record.package_id | ||||
|  | ||||
|  | ||||
| def test_in_package_context(database: SQLite, package_ahriman: Package, mocker: MockerFixture) -> None: | ||||
|  | ||||
		Reference in New Issue
	
	Block a user