mirror of
https://github.com/arcan1s/ahriman.git
synced 2026-11-11 15:30:29 +00:00
implement detailed build plan
This commit is contained in:
1 parent
dc4f2be65b
commit
f6c94d59cc
18 files changed
+491
-40
No files matched your search
@@ -21,8 +21,9 @@ from collections.abc import Callable, Iterable
|
|||||||
|
|
||||||
from ahriman.application.application.application_packages import ApplicationPackages
|
from ahriman.application.application.application_packages import ApplicationPackages
|
||||||
from ahriman.application.application.application_repository import ApplicationRepository
|
from ahriman.application.application.application_repository import ApplicationRepository
|
||||||
from ahriman.core.formatters import UpdatePrinter
|
from ahriman.core.formatters import BuildPlanPrinter, UpdatePrinter
|
||||||
from ahriman.core.tree import Tree
|
from ahriman.core.tree import Tree
|
||||||
|
from ahriman.models.build_plan import BuildPlan
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
from ahriman.models.result import Result
|
from ahriman.models.result import Result
|
||||||
|
|
||||||
@@ -95,6 +96,21 @@ class Application(ApplicationPackages, ApplicationRepository):
|
|||||||
"""
|
"""
|
||||||
self.repository.triggers.on_stop()
|
self.repository.triggers.on_stop()
|
||||||
|
|
||||||
|
def print_plan(self, plan: BuildPlan, *, log_fn: Callable[[str], None], bump_pkgrel: bool = False) -> None:
|
||||||
|
"""
|
||||||
|
print the packages and reasons in a build plan, grouped by dependency step
|
||||||
|
|
||||||
|
Args:
|
||||||
|
plan(BuildPlan): preview to display
|
||||||
|
log_fn(Callable[[str], None]): output function
|
||||||
|
bump_pkgrel(bool, optional): whether duplicate releases are expected to increment
|
||||||
|
"""
|
||||||
|
local_versions = {} if not plan.items else {
|
||||||
|
package.base: package.version for package in self.repository.packages()
|
||||||
|
}
|
||||||
|
BuildPlanPrinter(plan, local_versions, bump_pkgrel=bump_pkgrel)(
|
||||||
|
verbose=True, log_fn=log_fn, separator=" -> ")
|
||||||
|
|
||||||
def print_updates(self, packages: list[Package], *, log_fn: Callable[[str], None]) -> None:
|
def print_updates(self, packages: list[Package], *, log_fn: Callable[[str], None]) -> None:
|
||||||
"""
|
"""
|
||||||
print list of packages to be built. This method will build dependency tree and print updates accordingly
|
print list of packages to be built. This method will build dependency tree and print updates accordingly
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ from ahriman.application.application.application_properties import ApplicationPr
|
|||||||
from ahriman.application.application.workers import Updater
|
from ahriman.application.application.workers import Updater
|
||||||
from ahriman.core.build_tools.sources import Sources
|
from ahriman.core.build_tools.sources import Sources
|
||||||
from ahriman.core.exceptions import UnknownPackageError
|
from ahriman.core.exceptions import UnknownPackageError
|
||||||
|
from ahriman.models.build_plan import BuildPlan
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
from ahriman.models.packagers import Packagers
|
from ahriman.models.packagers import Packagers
|
||||||
from ahriman.models.result import Result
|
from ahriman.models.result import Result
|
||||||
@@ -33,6 +34,53 @@ class ApplicationRepository(ApplicationProperties):
|
|||||||
repository control class
|
repository control class
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def _collect_updates(self, filter_packages: Iterable[str], *, aur: bool, local: bool, manual: bool, vcs: bool,
|
||||||
|
check_files: bool) -> BuildPlan:
|
||||||
|
"""
|
||||||
|
collect update candidates for execution or preview
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filter_packages(Iterable[str]): restrict update checks to package bases
|
||||||
|
aur(bool): check AUR and official repository packages
|
||||||
|
local(bool): check locally sourced packages
|
||||||
|
manual(bool): include manually queued packages
|
||||||
|
vcs(bool): check VCS package versions
|
||||||
|
check_files(bool): check for broken implicit dependencies
|
||||||
|
Returns:
|
||||||
|
BuildPlan: selected update candidates and their reasons
|
||||||
|
"""
|
||||||
|
plan = BuildPlan()
|
||||||
|
updates = {}
|
||||||
|
|
||||||
|
# always add already built packages, because they will be always added
|
||||||
|
built = self.repository.load_archives(self.repository.packages_built())
|
||||||
|
updates.update({package.base: package for package in built})
|
||||||
|
for package in built:
|
||||||
|
plan.add(package, "already built")
|
||||||
|
|
||||||
|
if aur:
|
||||||
|
updates.update({package.base: package for package in self.repository.updates_aur(
|
||||||
|
filter_packages, vcs=vcs, plan=plan)})
|
||||||
|
if local:
|
||||||
|
updates.update({package.base: package for package in self.repository.updates_local(vcs=vcs, plan=plan)})
|
||||||
|
if manual:
|
||||||
|
updates.update({package.base: package for package in self.repository.updates_manual(plan=plan)})
|
||||||
|
if check_files:
|
||||||
|
updates.update({package.base: package for package in self.repository.updates_dependencies(
|
||||||
|
filter_packages, plan=plan)})
|
||||||
|
|
||||||
|
for package in updates.values():
|
||||||
|
plan.add(package)
|
||||||
|
selected = plan.packages
|
||||||
|
for package in selected:
|
||||||
|
requesters = [
|
||||||
|
candidate.base for candidate in selected if candidate.base != package.base and set(
|
||||||
|
package.packages_full).intersection(
|
||||||
|
candidate.depends_build)]
|
||||||
|
if requesters:
|
||||||
|
plan.add_dependency(package, requesters)
|
||||||
|
return plan
|
||||||
|
|
||||||
def changes(self, packages: Iterable[Package]) -> None:
|
def changes(self, packages: Iterable[Package]) -> None:
|
||||||
"""
|
"""
|
||||||
generate and update package changes
|
generate and update package changes
|
||||||
@@ -182,7 +230,7 @@ class ApplicationRepository(ApplicationProperties):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def updates(self, filter_packages: Iterable[str], *,
|
def updates(self, filter_packages: Iterable[str], *,
|
||||||
aur: bool, local: bool, manual: bool, vcs: bool, check_files: bool) -> list[Package]:
|
aur: bool, local: bool, manual: bool, vcs: bool, check_files: bool) -> BuildPlan:
|
||||||
"""
|
"""
|
||||||
get list of packages to run update process
|
get list of packages to run update process
|
||||||
|
|
||||||
@@ -193,25 +241,8 @@ class ApplicationRepository(ApplicationProperties):
|
|||||||
manual(bool): include or exclude manual updates
|
manual(bool): include or exclude manual updates
|
||||||
vcs(bool): enable or disable checking of VCS packages
|
vcs(bool): enable or disable checking of VCS packages
|
||||||
check_files(bool): check for broken dependencies
|
check_files(bool): check for broken dependencies
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list[Package]: list of out-of-dated packages
|
BuildPlan: selected packages and their reasons
|
||||||
"""
|
"""
|
||||||
updates = {}
|
return self._collect_updates(filter_packages, aur=aur, local=local, manual=manual, vcs=vcs,
|
||||||
|
check_files=check_files)
|
||||||
# always add already built packages, because they will be always added
|
|
||||||
updates.update({
|
|
||||||
package.base: package
|
|
||||||
for package in self.repository.load_archives(self.repository.packages_built())
|
|
||||||
})
|
|
||||||
|
|
||||||
if aur:
|
|
||||||
updates.update({package.base: package for package in self.repository.updates_aur(filter_packages, vcs=vcs)})
|
|
||||||
if local:
|
|
||||||
updates.update({package.base: package for package in self.repository.updates_local(vcs=vcs)})
|
|
||||||
if manual:
|
|
||||||
updates.update({package.base: package for package in self.repository.updates_manual()})
|
|
||||||
if check_files:
|
|
||||||
updates.update({package.base: package for package in self.repository.updates_dependencies(filter_packages)})
|
|
||||||
|
|
||||||
return [package for _, package in sorted(updates.items())]
|
|
||||||
@@ -147,13 +147,16 @@ class Update(Handler):
|
|||||||
application(Application): application instance
|
application(Application): application instance
|
||||||
args(argparse.Namespace): command line args
|
args(argparse.Namespace): command line args
|
||||||
"""
|
"""
|
||||||
packages = application.updates(args.package, aur=args.aur, local=args.local, manual=args.manual, vcs=args.vcs,
|
plan = application.updates(
|
||||||
check_files=args.check_files)
|
args.package, aur=args.aur, local=args.local, manual=args.manual, vcs=args.vcs,
|
||||||
|
check_files=args.check_files)
|
||||||
|
packages = plan.packages
|
||||||
if args.changes: # generate changes if requested
|
if args.changes: # generate changes if requested
|
||||||
application.changes(packages)
|
application.changes(packages)
|
||||||
|
|
||||||
if args.dry_run: # exit from application if no build requested
|
if args.dry_run: # exit from application if no build requested
|
||||||
Update.check_status(args.exit_code, packages) # status code check
|
Update.check_status(args.exit_code, packages) # status code check
|
||||||
|
application.print_plan(plan, log_fn=print, bump_pkgrel=args.increment)
|
||||||
return
|
return
|
||||||
|
|
||||||
packages = application.with_dependencies(packages, process_dependencies=args.dependencies)
|
packages = application.with_dependencies(packages, process_dependencies=args.dependencies)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
from ahriman.core.formatters.aur_printer import AurPrinter
|
from ahriman.core.formatters.aur_printer import AurPrinter
|
||||||
|
from ahriman.core.formatters.build_plan_printer import BuildPlanPrinter
|
||||||
from ahriman.core.formatters.build_printer import BuildPrinter
|
from ahriman.core.formatters.build_printer import BuildPrinter
|
||||||
from ahriman.core.formatters.changes_printer import ChangesPrinter
|
from ahriman.core.formatters.changes_printer import ChangesPrinter
|
||||||
from ahriman.core.formatters.configuration_paths_printer import ConfigurationPathsPrinter
|
from ahriman.core.formatters.configuration_paths_printer import ConfigurationPathsPrinter
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2021-2026 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 collections.abc import Callable
|
||||||
|
|
||||||
|
from ahriman.core.formatters.printer import Printer
|
||||||
|
from ahriman.core.formatters.update_printer import UpdatePrinter
|
||||||
|
from ahriman.core.tree import Tree
|
||||||
|
from ahriman.models.build_plan import BuildPlan
|
||||||
|
from ahriman.models.package import Package
|
||||||
|
|
||||||
|
|
||||||
|
class BuildPlanPrinter(Printer):
|
||||||
|
"""
|
||||||
|
print a build plan with dependency steps and package selection reasons
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
plan(BuildPlan): plan to display
|
||||||
|
local_versions(dict[str, str]): published package versions indexed by base
|
||||||
|
bump_pkgrel(bool): whether duplicate package releases are expected to increment
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, plan: BuildPlan, local_versions: dict[str, str], *, bump_pkgrel: bool = False) -> None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
plan(BuildPlan): plan to display
|
||||||
|
local_versions(dict[str, str]): published package versions indexed by base
|
||||||
|
bump_pkgrel(bool, optional): show an expected release increment (Default value = False)
|
||||||
|
"""
|
||||||
|
self.plan = plan
|
||||||
|
self.local_versions = local_versions
|
||||||
|
self.bump_pkgrel = bump_pkgrel
|
||||||
|
|
||||||
|
def _print_package(self, package: Package, *, verbose: bool, log_fn: Callable[[str], None],
|
||||||
|
separator: str) -> None:
|
||||||
|
"""
|
||||||
|
print a package and its selection reasons
|
||||||
|
|
||||||
|
Args:
|
||||||
|
package(Package): package to display
|
||||||
|
verbose(bool): include package selection reasons
|
||||||
|
log_fn(Callable[[str], None]): output function
|
||||||
|
separator(str): version separator
|
||||||
|
"""
|
||||||
|
UpdatePrinter(package, self.local_versions.get(package.base), bump_pkgrel=self.bump_pkgrel)(
|
||||||
|
verbose=True, log_fn=log_fn, separator=separator)
|
||||||
|
reasons = self.plan.item(package.base).reasons
|
||||||
|
if verbose and reasons:
|
||||||
|
log_fn(f"\tReasons: {'; '.join(reasons)}")
|
||||||
|
|
||||||
|
def print(self, *, verbose: bool, log_fn: Callable[[str], None] = Printer._print,
|
||||||
|
separator: str = " -> ") -> None:
|
||||||
|
"""
|
||||||
|
print the plan as numbered dependency steps
|
||||||
|
|
||||||
|
Args:
|
||||||
|
verbose(bool): include package selection reasons
|
||||||
|
log_fn(Callable[[str], None], optional): output function
|
||||||
|
separator(str, optional): version separator (Default value = " -> ")
|
||||||
|
"""
|
||||||
|
if not self.plan.items:
|
||||||
|
log_fn("No packages selected")
|
||||||
|
return
|
||||||
|
|
||||||
|
for index, level in enumerate(Tree.resolve(self.plan.packages), start=1):
|
||||||
|
log_fn(f"Step {index}:")
|
||||||
|
for package in level:
|
||||||
|
self._print_package(package, verbose=verbose, log_fn=log_fn, separator=separator)
|
||||||
@@ -32,15 +32,17 @@ class UpdatePrinter(StringPrinter):
|
|||||||
local_version(str | None): local version of the package if any
|
local_version(str | None): local version of the package if any
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, remote: Package, local_version: str | None) -> None:
|
def __init__(self, remote: Package, local_version: str | None, *, bump_pkgrel: bool = True) -> None:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
remote(Package): remote (new) package object
|
remote(Package): remote (new) package object
|
||||||
local_version(str | None): local version of the package if any
|
local_version(str | None): local version of the package if any
|
||||||
|
bump_pkgrel(bool, optional): show a release increment for duplicates (Default value = True)
|
||||||
"""
|
"""
|
||||||
StringPrinter.__init__(self, remote.base)
|
StringPrinter.__init__(self, remote.base)
|
||||||
self.package = remote
|
self.package = remote
|
||||||
self.local_version = local_version
|
self.local_version = local_version
|
||||||
|
self.bump_pkgrel = bump_pkgrel
|
||||||
|
|
||||||
def properties(self) -> list[Property]:
|
def properties(self) -> list[Property]:
|
||||||
"""
|
"""
|
||||||
@@ -49,7 +51,7 @@ class UpdatePrinter(StringPrinter):
|
|||||||
Returns:
|
Returns:
|
||||||
list[Property]: list of content properties
|
list[Property]: list of content properties
|
||||||
"""
|
"""
|
||||||
if (pkgrel := self.package.next_pkgrel(self.local_version)) is not None:
|
if self.bump_pkgrel and (pkgrel := self.package.next_pkgrel(self.local_version)) is not None:
|
||||||
epoch, pkgver, _ = parse_version(self.package.version)
|
epoch, pkgver, _ = parse_version(self.package.version)
|
||||||
effective_new_version = full_version(epoch, pkgver, pkgrel)
|
effective_new_version = full_version(epoch, pkgver, pkgrel)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ from ahriman.core.build_tools.sources import Sources
|
|||||||
from ahriman.core.exceptions import UnknownPackageError
|
from ahriman.core.exceptions import UnknownPackageError
|
||||||
from ahriman.core.repository.cleaner import Cleaner
|
from ahriman.core.repository.cleaner import Cleaner
|
||||||
from ahriman.core.repository.package_info import PackageInfo
|
from ahriman.core.repository.package_info import PackageInfo
|
||||||
|
from ahriman.models.build_plan import BuildPlan
|
||||||
from ahriman.models.build_status import BuildStatusEnum
|
from ahriman.models.build_status import BuildStatusEnum
|
||||||
from ahriman.models.event import EventType
|
from ahriman.models.event import EventType
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
@@ -47,13 +48,14 @@ class UpdateHandler(PackageInfo, Cleaner):
|
|||||||
package.base for package, status in self.reporter.package_get(None) if status.is_held
|
package.base for package, status in self.reporter.package_get(None) if status.is_held
|
||||||
}
|
}
|
||||||
|
|
||||||
def updates_aur(self, filter_packages: Iterable[str], *, vcs: bool) -> list[Package]:
|
def updates_aur(self, filter_packages: Iterable[str], *, vcs: bool, plan: BuildPlan | None = None) -> list[Package]:
|
||||||
"""
|
"""
|
||||||
check AUR for updates
|
check AUR for updates
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
filter_packages(Iterable[str]): do not check every package just specified in the list
|
filter_packages(Iterable[str]): do not check every package just specified in the list
|
||||||
vcs(bool): enable or disable checking of VCS packages
|
vcs(bool): enable or disable checking of VCS packages
|
||||||
|
plan(BuildPlan | None, optional): record update reasons for a preview
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list[Package]: list of packages which are out-of-dated
|
list[Package]: list of packages which are out-of-dated
|
||||||
@@ -87,6 +89,10 @@ class UpdateHandler(PackageInfo, Cleaner):
|
|||||||
self.reporter.set_pending(local.base)
|
self.reporter.set_pending(local.base)
|
||||||
self.event(local.base, EventType.PackageOutdated, "Remote version is newer than local")
|
self.event(local.base, EventType.PackageOutdated, "Remote version is newer than local")
|
||||||
result.append(remote)
|
result.append(remote)
|
||||||
|
if plan is not None:
|
||||||
|
reason = "official repository update" if local.remote.source == PackageSource.Repository \
|
||||||
|
else "AUR update"
|
||||||
|
plan.add(remote, reason)
|
||||||
else:
|
else:
|
||||||
self.reporter.package_status_update(local.base, BuildStatusEnum.Success)
|
self.reporter.package_status_update(local.base, BuildStatusEnum.Success)
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -95,13 +101,14 @@ class UpdateHandler(PackageInfo, Cleaner):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def updates_dependencies(self, filter_packages: Iterable[str]) -> list[Package]:
|
def updates_dependencies(self, filter_packages: Iterable[str], *, plan: BuildPlan | None = None) -> list[Package]:
|
||||||
"""
|
"""
|
||||||
check packages which are required to be rebuilt based on dynamic dependencies
|
check packages which are required to be rebuilt based on dynamic dependencies
|
||||||
(e.g. linking, modules paths, etc.)
|
(e.g. linking, modules paths, etc.)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
filter_packages(Iterable[str]): do not check every package just specified in the list
|
filter_packages(Iterable[str]): do not check every package just specified in the list
|
||||||
|
plan(BuildPlan | None, optional): record dependency reasons for a preview
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list[Package]: list of packages for which there is breaking linking
|
list[Package]: list of packages for which there is breaking linking
|
||||||
@@ -138,17 +145,20 @@ class UpdateHandler(PackageInfo, Cleaner):
|
|||||||
self.reporter.set_pending(local.base)
|
self.reporter.set_pending(local.base)
|
||||||
self.event(local.base, EventType.PackageOutdated, "Implicit dependencies are broken")
|
self.event(local.base, EventType.PackageOutdated, "Implicit dependencies are broken")
|
||||||
result.append(local)
|
result.append(local)
|
||||||
|
if plan is not None:
|
||||||
|
plan.add(local, "broken implicit dependency")
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def updates_local(self, *, vcs: bool) -> list[Package]:
|
def updates_local(self, *, vcs: bool, plan: BuildPlan | None = None) -> list[Package]:
|
||||||
"""
|
"""
|
||||||
check local packages for updates
|
check local packages for updates
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
vcs(bool): enable or disable checking of VCS packages
|
vcs(bool): enable or disable checking of VCS packages
|
||||||
|
plan(BuildPlan | None, optional): record update reasons for a preview
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list[Package]: list of local packages which are out-of-dated
|
list[Package]: list of local packages which are out-of-dated
|
||||||
@@ -188,12 +198,14 @@ class UpdateHandler(PackageInfo, Cleaner):
|
|||||||
self.reporter.set_pending(local.base)
|
self.reporter.set_pending(local.base)
|
||||||
self.event(local.base, EventType.PackageOutdated, "Locally pulled sources are outdated")
|
self.event(local.base, EventType.PackageOutdated, "Locally pulled sources are outdated")
|
||||||
result.append(remote)
|
result.append(remote)
|
||||||
|
if plan is not None:
|
||||||
|
plan.add(remote, "local source update")
|
||||||
except Exception:
|
except Exception:
|
||||||
self.logger.exception("could not process package at %s", cache_dir)
|
self.logger.exception("could not process package at %s", cache_dir)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def updates_manual(self) -> list[Package]:
|
def updates_manual(self, *, plan: BuildPlan | None = None) -> list[Package]:
|
||||||
"""
|
"""
|
||||||
check for packages for which manual update has been requested
|
check for packages for which manual update has been requested
|
||||||
|
|
||||||
@@ -206,6 +218,8 @@ class UpdateHandler(PackageInfo, Cleaner):
|
|||||||
try:
|
try:
|
||||||
for local in self.database.build_queue_get():
|
for local in self.database.build_queue_get():
|
||||||
result.append(local)
|
result.append(local)
|
||||||
|
if plan is not None:
|
||||||
|
plan.add(local, "queued request")
|
||||||
if local.base not in known_bases:
|
if local.base not in known_bases:
|
||||||
self.reporter.set_unknown(local)
|
self.reporter.set_unknown(local)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2021-2026 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 collections.abc import Iterator
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
from ahriman.models.package import Package
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class BuildPlanItem:
|
||||||
|
"""
|
||||||
|
one package and the reasons it appears in a build plan
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
package(Package): package selected for the plan
|
||||||
|
reasons(list[str]): reasons why the package was selected
|
||||||
|
"""
|
||||||
|
|
||||||
|
package: Package
|
||||||
|
reasons: list[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
def add_reason(self, reason: str) -> None:
|
||||||
|
"""
|
||||||
|
add a reason unless it is already present
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reason(str): reason for selecting the package
|
||||||
|
"""
|
||||||
|
if reason not in self.reasons:
|
||||||
|
self.reasons.append(reason)
|
||||||
|
|
||||||
|
|
||||||
|
class BuildPlan:
|
||||||
|
"""
|
||||||
|
in-memory preview of the packages selected for a build operation
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
_items(dict[str, BuildPlanItem]): plan items indexed by package base
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
"""
|
||||||
|
initialize an empty build plan
|
||||||
|
"""
|
||||||
|
self._items: dict[str, BuildPlanItem] = {}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def items(self) -> list[BuildPlanItem]:
|
||||||
|
"""
|
||||||
|
get plan items in stable package-base order
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[BuildPlanItem]: selected plan items
|
||||||
|
"""
|
||||||
|
return [self._items[base] for base in sorted(self._items)]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def packages(self) -> list[Package]:
|
||||||
|
"""
|
||||||
|
get packages in stable package-base order
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[Package]: selected packages
|
||||||
|
"""
|
||||||
|
return [item.package for item in self.items]
|
||||||
|
|
||||||
|
def add(self, package: Package, reason: str | None = None) -> BuildPlanItem:
|
||||||
|
"""
|
||||||
|
add or update a package in the plan
|
||||||
|
|
||||||
|
Args:
|
||||||
|
package(Package): package selected for the plan
|
||||||
|
reason(str | None, optional): reason for selecting it
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
BuildPlanItem: plan item for the package
|
||||||
|
"""
|
||||||
|
item = self._items.setdefault(package.base, BuildPlanItem(package))
|
||||||
|
item.package = package
|
||||||
|
if reason is not None:
|
||||||
|
item.add_reason(reason)
|
||||||
|
return item
|
||||||
|
|
||||||
|
def add_dependency(self, package: Package, requesters: list[str]) -> None:
|
||||||
|
"""
|
||||||
|
add a dependency and identify the selected packages that require it
|
||||||
|
|
||||||
|
Args:
|
||||||
|
package(Package): dependency package
|
||||||
|
requesters(list[str]): package bases requiring this dependency
|
||||||
|
"""
|
||||||
|
item = self.add(package)
|
||||||
|
for requester in sorted(requesters):
|
||||||
|
item.add_reason(f"required by {requester}")
|
||||||
|
if not requesters:
|
||||||
|
item.add_reason("required dependency")
|
||||||
|
|
||||||
|
def item(self, package_base: str) -> BuildPlanItem:
|
||||||
|
"""
|
||||||
|
get a plan item by package base
|
||||||
|
|
||||||
|
Args:
|
||||||
|
package_base(str): package base to look up
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
BuildPlanItem: matching item
|
||||||
|
"""
|
||||||
|
return self._items[package_base]
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[Package]:
|
||||||
|
"""Iterate over selected packages in stable package-base order."""
|
||||||
|
return iter(self.packages)
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
"""Return the number of selected packages."""
|
||||||
|
return len(self._items)
|
||||||
@@ -5,6 +5,7 @@ from unittest.mock import MagicMock, call as MockCall
|
|||||||
|
|
||||||
from ahriman.application.application import Application
|
from ahriman.application.application import Application
|
||||||
from ahriman.core.tree import Leaf, Tree
|
from ahriman.core.tree import Leaf, Tree
|
||||||
|
from ahriman.models.build_plan import BuildPlan
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
from ahriman.models.result import Result
|
from ahriman.models.result import Result
|
||||||
|
|
||||||
@@ -63,6 +64,31 @@ def test_print_updates(application: Application, package_ahriman: Package, mocke
|
|||||||
print_mock.assert_called_once_with(verbose=True, log_fn=print, separator=" -> ")
|
print_mock.assert_called_once_with(verbose=True, log_fn=print, separator=" -> ")
|
||||||
|
|
||||||
|
|
||||||
|
def test_print_plan(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||||
|
"""A plan preview prints a dependency step, version, and selection reason."""
|
||||||
|
plan = BuildPlan()
|
||||||
|
plan.add(package_ahriman, "AUR update")
|
||||||
|
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
|
||||||
|
mocker.patch("ahriman.core.tree.Tree.resolve", return_value=[[package_ahriman]])
|
||||||
|
output: list[str] = []
|
||||||
|
|
||||||
|
application.print_plan(plan, log_fn=output.append)
|
||||||
|
|
||||||
|
assert output == [
|
||||||
|
"Step 1:", package_ahriman.base, f"\t{package_ahriman.version} -> {package_ahriman.version}",
|
||||||
|
"\tReasons: AUR update",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_print_empty_plan(application: Application) -> None:
|
||||||
|
"""An empty preview explains why it has no package lines."""
|
||||||
|
output: list[str] = []
|
||||||
|
|
||||||
|
application.print_plan(BuildPlan(), log_fn=output.append)
|
||||||
|
|
||||||
|
assert output == ["No packages selected"]
|
||||||
|
|
||||||
|
|
||||||
def test_with_dependencies(application: Application, package_ahriman: Package, package_python_schedule: Package,
|
def test_with_dependencies(application: Application, package_ahriman: Package, package_python_schedule: Package,
|
||||||
mocker: MockerFixture) -> None:
|
mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from unittest.mock import call as MockCall
|
|||||||
from ahriman.application.application.application_repository import ApplicationRepository
|
from ahriman.application.application.application_repository import ApplicationRepository
|
||||||
from ahriman.core.exceptions import UnknownPackageError
|
from ahriman.core.exceptions import UnknownPackageError
|
||||||
from ahriman.core.tree import Leaf, Tree
|
from ahriman.core.tree import Leaf, Tree
|
||||||
|
from ahriman.models.build_plan import BuildPlan
|
||||||
from ahriman.models.changes import Changes
|
from ahriman.models.changes import Changes
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
from ahriman.models.packagers import Packagers
|
from ahriman.models.packagers import Packagers
|
||||||
@@ -270,6 +271,30 @@ def test_updates_all(application_repository: ApplicationRepository, package_ahri
|
|||||||
updates_deps_mock.assert_called_once_with([])
|
updates_deps_mock.assert_called_once_with([])
|
||||||
|
|
||||||
|
|
||||||
|
def test_updates_returns_plan(application_repository: ApplicationRepository, package_ahriman: Package,
|
||||||
|
package_python_schedule: Package,
|
||||||
|
mocker: MockerFixture) -> None:
|
||||||
|
"""Update discovery records reasons in its build plan."""
|
||||||
|
mocker.patch("ahriman.core.repository.package_info.PackageInfo.packages_built", return_value=[Path("built")])
|
||||||
|
mocker.patch(
|
||||||
|
"ahriman.core.repository.package_info.PackageInfo.load_archives",
|
||||||
|
return_value=[package_python_schedule])
|
||||||
|
package_ahriman.packages[package_ahriman.base].make_depends = [package_python_schedule.base]
|
||||||
|
|
||||||
|
def collect(_filter: list[str], *, vcs: bool, plan: BuildPlan) -> list[Package]:
|
||||||
|
assert vcs
|
||||||
|
plan.add(package_ahriman, "AUR update")
|
||||||
|
return [package_ahriman]
|
||||||
|
|
||||||
|
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur", side_effect=collect)
|
||||||
|
|
||||||
|
plan = application_repository.updates([], aur=True, local=False, manual=False, vcs=True, check_files=False)
|
||||||
|
|
||||||
|
assert list(plan) == [package_ahriman, package_python_schedule]
|
||||||
|
assert plan.item(package_ahriman.base).reasons == ["AUR update"]
|
||||||
|
assert plan.item(package_python_schedule.base).reasons == ["already built", f"required by {package_ahriman.base}"]
|
||||||
|
|
||||||
|
|
||||||
def test_updates_disabled(application_repository: ApplicationRepository, mocker: MockerFixture) -> None:
|
def test_updates_disabled(application_repository: ApplicationRepository, mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must get updates without anything
|
must get updates without anything
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from ahriman.application.handlers.update import Update
|
|||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.repository import Repository
|
from ahriman.core.repository import Repository
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
|
from ahriman.models.build_plan import BuildPlan
|
||||||
from ahriman.models.packagers import Packagers
|
from ahriman.models.packagers import Packagers
|
||||||
from ahriman.models.result import Result
|
from ahriman.models.result import Result
|
||||||
|
|
||||||
@@ -91,7 +92,8 @@ def test_perform_action_empty_exception(args: argparse.Namespace, application: A
|
|||||||
args = _default_args(args)
|
args = _default_args(args)
|
||||||
args.exit_code = True
|
args.exit_code = True
|
||||||
args.dry_run = True
|
args.dry_run = True
|
||||||
mocker.patch("ahriman.application.application.Application.updates", return_value=[])
|
mocker.patch("ahriman.application.application.Application.updates", return_value=BuildPlan())
|
||||||
|
mocker.patch("ahriman.application.application.Application.print_plan")
|
||||||
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status")
|
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status")
|
||||||
|
|
||||||
Update.perform_action(application, args)
|
Update.perform_action(application, args)
|
||||||
@@ -106,7 +108,9 @@ def test_perform_action_update_empty_exception(args: argparse.Namespace, applica
|
|||||||
args = _default_args(args)
|
args = _default_args(args)
|
||||||
args.exit_code = True
|
args.exit_code = True
|
||||||
mocker.patch("ahriman.application.application.Application.update", return_value=Result())
|
mocker.patch("ahriman.application.application.Application.update", return_value=Result())
|
||||||
mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman])
|
plan = BuildPlan()
|
||||||
|
plan.add(package_ahriman)
|
||||||
|
mocker.patch("ahriman.application.application.Application.updates", return_value=plan)
|
||||||
mocker.patch("ahriman.application.application.Application.with_dependencies", return_value=[package_ahriman])
|
mocker.patch("ahriman.application.application.Application.with_dependencies", return_value=[package_ahriman])
|
||||||
mocker.patch("ahriman.application.application.Application.print_updates")
|
mocker.patch("ahriman.application.application.Application.print_updates")
|
||||||
mocker.patch("ahriman.application.application.Application.changes")
|
mocker.patch("ahriman.application.application.Application.changes")
|
||||||
@@ -125,14 +129,17 @@ def test_perform_action_dry_run(args: argparse.Namespace, application: Applicati
|
|||||||
args.dry_run = True
|
args.dry_run = True
|
||||||
application_mock = mocker.patch("ahriman.application.application.Application.update")
|
application_mock = mocker.patch("ahriman.application.application.Application.update")
|
||||||
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status")
|
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status")
|
||||||
updates_mock = mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman])
|
plan = BuildPlan()
|
||||||
|
plan.add(package_ahriman, "AUR update")
|
||||||
|
updates_mock = mocker.patch("ahriman.application.application.Application.updates", return_value=plan)
|
||||||
changes_mock = mocker.patch("ahriman.application.application.Application.changes")
|
changes_mock = mocker.patch("ahriman.application.application.Application.changes")
|
||||||
|
print_mock = mocker.patch("ahriman.application.application.Application.print_plan")
|
||||||
|
|
||||||
Update.perform_action(application, args)
|
Update.perform_action(application, args)
|
||||||
updates_mock.assert_called_once_with(
|
updates_mock.assert_called_once()
|
||||||
args.package, aur=args.aur, local=args.local, manual=args.manual, vcs=args.vcs, check_files=args.check_files)
|
|
||||||
application_mock.assert_not_called()
|
application_mock.assert_not_called()
|
||||||
changes_mock.assert_called_once_with([package_ahriman])
|
changes_mock.assert_called_once_with([package_ahriman])
|
||||||
|
print_mock.assert_called_once_with(plan, log_fn=print, bump_pkgrel=args.increment)
|
||||||
check_mock.assert_called_once_with(False, [package_ahriman])
|
check_mock.assert_called_once_with(False, [package_ahriman])
|
||||||
|
|
||||||
|
|
||||||
@@ -145,7 +152,8 @@ def test_perform_action_no_changes(args: argparse.Namespace, application: Applic
|
|||||||
args.changes = False
|
args.changes = False
|
||||||
mocker.patch("ahriman.application.application.Application.update")
|
mocker.patch("ahriman.application.application.Application.update")
|
||||||
mocker.patch("ahriman.application.handlers.handler.Handler.check_status")
|
mocker.patch("ahriman.application.handlers.handler.Handler.check_status")
|
||||||
mocker.patch("ahriman.application.application.Application.updates")
|
mocker.patch("ahriman.application.application.Application.updates", return_value=BuildPlan())
|
||||||
|
mocker.patch("ahriman.application.application.Application.print_plan")
|
||||||
changes_mock = mocker.patch("ahriman.application.application.Application.changes")
|
changes_mock = mocker.patch("ahriman.application.application.Application.changes")
|
||||||
|
|
||||||
Update.perform_action(application, args)
|
Update.perform_action(application, args)
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
from ahriman.core.formatters import BuildPlanPrinter
|
||||||
|
from ahriman.models.build_plan import BuildPlan
|
||||||
|
from ahriman.models.package import Package
|
||||||
|
|
||||||
|
|
||||||
|
def test_print_plan(package_ahriman: Package) -> None:
|
||||||
|
"""The plan printer owns step, version, and reason output."""
|
||||||
|
plan = BuildPlan()
|
||||||
|
plan.add(package_ahriman, "AUR update")
|
||||||
|
output: list[str] = []
|
||||||
|
|
||||||
|
BuildPlanPrinter(plan, {package_ahriman.base: package_ahriman.version})(
|
||||||
|
verbose=True, log_fn=output.append, separator=" -> ")
|
||||||
|
|
||||||
|
assert output == [
|
||||||
|
"Step 1:", package_ahriman.base,
|
||||||
|
f"\t{package_ahriman.version} -> {package_ahriman.version}", "\tReasons: AUR update",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_print_empty_plan() -> None:
|
||||||
|
"""The plan printer explains an empty preview."""
|
||||||
|
output: list[str] = []
|
||||||
|
|
||||||
|
BuildPlanPrinter(BuildPlan(), {})(verbose=True, log_fn=output.append)
|
||||||
|
|
||||||
|
assert output == ["No packages selected"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_print_plan_without_reasons(package_ahriman: Package) -> None:
|
||||||
|
"""The concise printer mode omits reasons while retaining package versions."""
|
||||||
|
plan = BuildPlan()
|
||||||
|
plan.add(package_ahriman)
|
||||||
|
output: list[str] = []
|
||||||
|
|
||||||
|
BuildPlanPrinter(plan, {})(verbose=False, log_fn=output.append, separator=" -> ")
|
||||||
|
|
||||||
|
assert output == ["Step 1:", package_ahriman.base, f"\tN/A -> {package_ahriman.version}"]
|
||||||
@@ -34,6 +34,15 @@ def test_properties_keep_pkgrel(update_printer: UpdatePrinter) -> None:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_properties_no_increment(update_printer: UpdatePrinter) -> None:
|
||||||
|
"""The preview honors a disabled release increment."""
|
||||||
|
update_printer.local_version = update_printer.package.version
|
||||||
|
update_printer.bump_pkgrel = False
|
||||||
|
assert update_printer.properties() == [
|
||||||
|
Property(update_printer.local_version, update_printer.package.version, is_required=True),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_title(update_printer: UpdatePrinter) -> None:
|
def test_title(update_printer: UpdatePrinter) -> None:
|
||||||
"""
|
"""
|
||||||
must return non-empty title
|
must return non-empty title
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from typing import Any
|
|||||||
|
|
||||||
from ahriman.core.exceptions import UnknownPackageError
|
from ahriman.core.exceptions import UnknownPackageError
|
||||||
from ahriman.core.repository.update_handler import UpdateHandler
|
from ahriman.core.repository.update_handler import UpdateHandler
|
||||||
|
from ahriman.models.build_plan import BuildPlan
|
||||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||||
from ahriman.models.dependencies import Dependencies
|
from ahriman.models.dependencies import Dependencies
|
||||||
from ahriman.models.event import EventType
|
from ahriman.models.event import EventType
|
||||||
@@ -117,8 +118,10 @@ def test_updates_aur_filter(update_handler: UpdateHandler, package_ahriman: Pack
|
|||||||
return_value=[package_ahriman])
|
return_value=[package_ahriman])
|
||||||
mocker.patch("ahriman.core.build_tools.package_version.PackageVersion.is_outdated", return_value=True)
|
mocker.patch("ahriman.core.build_tools.package_version.PackageVersion.is_outdated", return_value=True)
|
||||||
package_load_mock = mocker.patch("ahriman.models.package.Package.from_aur", return_value=package_ahriman)
|
package_load_mock = mocker.patch("ahriman.models.package.Package.from_aur", return_value=package_ahriman)
|
||||||
|
plan = BuildPlan()
|
||||||
|
|
||||||
assert update_handler.updates_aur([package_ahriman.base], vcs=True) == [package_ahriman]
|
assert update_handler.updates_aur([package_ahriman.base], vcs=True, plan=plan) == [package_ahriman]
|
||||||
|
assert plan.item(package_ahriman.base).reasons == ["AUR update"]
|
||||||
packages_mock.assert_called_once_with([package_ahriman.base])
|
packages_mock.assert_called_once_with([package_ahriman.base])
|
||||||
package_load_mock.assert_called_once_with(package_ahriman.base, None)
|
package_load_mock.assert_called_once_with(package_ahriman.base, None)
|
||||||
|
|
||||||
@@ -192,6 +195,7 @@ def test_updates_dependencies(update_handler: UpdateHandler, package_ahriman: Pa
|
|||||||
return_value=[package_ahriman, package_python_schedule])
|
return_value=[package_ahriman, package_python_schedule])
|
||||||
status_client_mock = mocker.patch("ahriman.core.status.Client.set_pending")
|
status_client_mock = mocker.patch("ahriman.core.status.Client.set_pending")
|
||||||
event_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.event")
|
event_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.event")
|
||||||
|
plan = BuildPlan()
|
||||||
dependencies = {
|
dependencies = {
|
||||||
package_ahriman.base: Dependencies({"usr/lib/python3.11/site-packages": ["python"]}),
|
package_ahriman.base: Dependencies({"usr/lib/python3.11/site-packages": ["python"]}),
|
||||||
package_python_schedule.base: Dependencies({"usr/lib/python3.12/site-packages": ["python"]}),
|
package_python_schedule.base: Dependencies({"usr/lib/python3.12/site-packages": ["python"]}),
|
||||||
@@ -201,7 +205,8 @@ def test_updates_dependencies(update_handler: UpdateHandler, package_ahriman: Pa
|
|||||||
mocker.patch("ahriman.core.alpm.pacman.Pacman.files",
|
mocker.patch("ahriman.core.alpm.pacman.Pacman.files",
|
||||||
return_value={"python": {"usr/lib/python3.12/site-packages"}})
|
return_value={"python": {"usr/lib/python3.12/site-packages"}})
|
||||||
|
|
||||||
assert update_handler.updates_dependencies(["filter"]) == [package_ahriman]
|
assert update_handler.updates_dependencies(["filter"], plan=plan) == [package_ahriman]
|
||||||
|
assert plan.item(package_ahriman.base).reasons == ["broken implicit dependency"]
|
||||||
packages_mock.assert_called_once_with(["filter"])
|
packages_mock.assert_called_once_with(["filter"])
|
||||||
status_client_mock.assert_called_once_with(package_ahriman.base)
|
status_client_mock.assert_called_once_with(package_ahriman.base)
|
||||||
event_mock.assert_called_once_with(package_ahriman.base, EventType.PackageOutdated,
|
event_mock.assert_called_once_with(package_ahriman.base, EventType.PackageOutdated,
|
||||||
@@ -263,8 +268,10 @@ def test_updates_local(update_handler: UpdateHandler, package_ahriman: Package,
|
|||||||
event_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.event")
|
event_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.event")
|
||||||
package_is_outdated_mock = mocker.patch("ahriman.core.build_tools.package_version.PackageVersion.is_outdated",
|
package_is_outdated_mock = mocker.patch("ahriman.core.build_tools.package_version.PackageVersion.is_outdated",
|
||||||
return_value=True)
|
return_value=True)
|
||||||
|
plan = BuildPlan()
|
||||||
|
|
||||||
assert update_handler.updates_local(vcs=True) == [package_ahriman]
|
assert update_handler.updates_local(vcs=True, plan=plan) == [package_ahriman]
|
||||||
|
assert plan.item(package_ahriman.base).reasons == ["local source update"]
|
||||||
fetch_mock.assert_called_once_with(Path(package_ahriman.base), pytest.helpers.anyvar(int))
|
fetch_mock.assert_called_once_with(Path(package_ahriman.base), pytest.helpers.anyvar(int))
|
||||||
package_load_mock.assert_called_once_with(Path(package_ahriman.base), "x86_64", None)
|
package_load_mock.assert_called_once_with(Path(package_ahriman.base), "x86_64", None)
|
||||||
status_client_mock.assert_called_once_with(package_ahriman.base)
|
status_client_mock.assert_called_once_with(package_ahriman.base)
|
||||||
@@ -368,8 +375,10 @@ def test_updates_manual_preserves_queue(update_handler: UpdateHandler, package_a
|
|||||||
"""
|
"""
|
||||||
update_handler.database.build_queue_insert(package_ahriman)
|
update_handler.database.build_queue_insert(package_ahriman)
|
||||||
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman])
|
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman])
|
||||||
|
plan = BuildPlan()
|
||||||
|
|
||||||
assert update_handler.updates_manual() == [package_ahriman]
|
assert update_handler.updates_manual(plan=plan) == [package_ahriman]
|
||||||
|
assert plan.item(package_ahriman.base).reasons == ["queued request"]
|
||||||
assert update_handler.database.build_queue_get() == [package_ahriman]
|
assert update_handler.database.build_queue_get() == [package_ahriman]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
from ahriman.models.build_plan import BuildPlan
|
||||||
|
from ahriman.models.package import Package
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_and_merge_plan_items(package_ahriman: Package, package_python_schedule: Package) -> None:
|
||||||
|
"""A plan keeps one current package per base and accumulates distinct reasons."""
|
||||||
|
plan = BuildPlan()
|
||||||
|
|
||||||
|
item = plan.add(package_ahriman, "AUR update")
|
||||||
|
plan.add(package_ahriman, "AUR update")
|
||||||
|
plan.add(package_ahriman, "queued request")
|
||||||
|
plan.add(package_python_schedule)
|
||||||
|
|
||||||
|
assert item.reasons == ["AUR update", "queued request"]
|
||||||
|
assert plan.packages == sorted([package_ahriman, package_python_schedule], key=lambda package: package.base)
|
||||||
|
assert plan.item(package_ahriman.base) is item
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_dependency(package_python_schedule: Package) -> None:
|
||||||
|
"""A plan records which selected package requires a dependency."""
|
||||||
|
plan = BuildPlan()
|
||||||
|
|
||||||
|
plan.add_dependency(package_python_schedule, ["ahriman", "python-ahriman"])
|
||||||
|
|
||||||
|
assert plan.item(package_python_schedule.base).reasons == ["required by ahriman", "required by python-ahriman"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_dependency_without_requesters(package_python_schedule: Package) -> None:
|
||||||
|
"""A dependency without a known direct requester still receives a reason."""
|
||||||
|
plan = BuildPlan()
|
||||||
|
|
||||||
|
plan.add_dependency(package_python_schedule, [])
|
||||||
|
|
||||||
|
assert plan.item(package_python_schedule.base).reasons == ["required dependency"]
|
||||||
@@ -4,6 +4,14 @@ ahriman.core.formatters package
|
|||||||
Submodules
|
Submodules
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
ahriman.core.formatters.build\_plan\_printer module
|
||||||
|
----------------------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: ahriman.core.formatters.build_plan_printer
|
||||||
|
:members:
|
||||||
|
:no-undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.core.formatters.aur\_printer module
|
ahriman.core.formatters.aur\_printer module
|
||||||
-------------------------------------------
|
-------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,14 @@ ahriman.models.auth\_settings module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
ahriman.models.build\_plan module
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
.. automodule:: ahriman.models.build_plan
|
||||||
|
:members:
|
||||||
|
:no-undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.models.build\_status module
|
ahriman.models.build\_status module
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -274,6 +274,8 @@ In this scenario, the update process must be separated into several stages. Firs
|
|||||||
|
|
||||||
sudo -u ahriman ahriman repo-check
|
sudo -u ahriman ahriman repo-check
|
||||||
|
|
||||||
|
The command prints selected packages in dependency steps, with the old and proposed versions and the reason each package was selected. ``repo-update --dry-run`` uses the same preview. Proposed ``pkgrel`` values follow the ``--increment`` option. The preview describes current metadata; it does not freeze sources or guarantee later build inputs.
|
||||||
|
|
||||||
During the check process, the service will generate changes from the last known commit and will send it to remote service. In order to verify source files changes, the web interface or special subcommand can be used:
|
During the check process, the service will generate changes from the last known commit and will send it to remote service. In order to verify source files changes, the web interface or special subcommand can be used:
|
||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
|
|||||||
Reference in new issue
Block a user