Compare commits

...

11 Commits

Author SHA1 Message Date
9410c521a1 Release 0.11.5 2021-03-13 05:18:44 +03:00
dd42cd0cd6 sort package list 2021-03-13 05:18:27 +03:00
50b409cd3e Release 0.11.4 2021-03-13 05:13:33 +03:00
356cd35c5f better templating 2021-03-13 05:12:53 +03:00
3405105dce pretty status html 2021-03-13 03:57:27 +03:00
4445c8c871 Release 0.11.3 2021-03-13 02:27:38 +03:00
a3a66c7c9a count epoch 2021-03-13 02:27:27 +03:00
45b762e3d9 Release 0.11.2 2021-03-13 01:57:26 +03:00
c5db7e64ca process prepare call for vcs packages 2021-03-13 01:57:10 +03:00
0dd4d098f6 Release 0.11.1 2021-03-12 00:24:49 +03:00
4866548224 handle built packages during update 2021-03-12 00:24:26 +03:00
16 changed files with 254 additions and 140 deletions

View File

@ -1,7 +1,7 @@
# Maintainer: Evgeniy Alekseev # Maintainer: Evgeniy Alekseev
pkgname='ahriman' pkgname='ahriman'
pkgver=0.11.0 pkgver=0.11.5
pkgrel=1 pkgrel=1
pkgdesc="ArcHlinux ReposItory MANager" pkgdesc="ArcHlinux ReposItory MANager"
arch=('any') arch=('any')
@ -23,7 +23,7 @@ optdepends=('aws-cli: sync to s3'
source=("https://github.com/arcan1s/ahriman/releases/download/$pkgver/$pkgname-$pkgver-src.tar.xz" source=("https://github.com/arcan1s/ahriman/releases/download/$pkgver/$pkgname-$pkgver-src.tar.xz"
'ahriman.sysusers' 'ahriman.sysusers'
'ahriman.tmpfiles') 'ahriman.tmpfiles')
sha512sums=('02a586de3253908022ec0d8329d217dc52c922ab091d3c6ed91bb45fbc521de08f9163c17dfb7589422f43e7114b0b3dd6d35357c4cd82e564251a0f7f908d18' sha512sums=('f81de308af2d462e4904b143808bd4f22c5479fc2ac56c6a6f562be08816d97ddc6daae0b453db98cbc236ce0abad4100f2ae4729a7444a16fbf4b61b2b7d655'
'13718afec2c6786a18f0b223ef8e58dccf0688bca4cdbe203f14071f5031ed20120eb0ce38b52c76cfd6e8b6581a9c9eaa2743eb11abbaca637451a84c33f075' '13718afec2c6786a18f0b223ef8e58dccf0688bca4cdbe203f14071f5031ed20120eb0ce38b52c76cfd6e8b6581a9c9eaa2743eb11abbaca637451a84c33f075'
'55b20f6da3d66e7bbf2add5d95a3b60632df121717d25a993e56e737d14f51fe063eb6f1b38bd81cc32e05db01c0c1d80aaa720c45cde87f238d8b46cdb8cbc4') '55b20f6da3d66e7bbf2add5d95a3b60632df121717d25a993e56e737d14f51fe063eb6f1b38bd81cc32e05db01c0c1d80aaa720c45cde87f238d8b46cdb8cbc4')
backup=('etc/ahriman.ini' backup=('etc/ahriman.ini'

View File

@ -3,84 +3,42 @@
<head> <head>
<title>{{ repository|e }}</title> <title>{{ repository|e }}</title>
<style> {% include "style.jinja2" %}
:root {
--color-building: 250, 255, 146;
--color-failed: 255, 94, 94;
--color-pending: 250, 255, 146;
--color-success: 121, 255, 94;
--color-unknown: 197, 197, 197;
}
@keyframes blink-building { {% include "sorttable.jinja2" %}
0% { background-color: rgba(var(--color-building), 1.0); } {% include "search.jinja2" %}
10% { background-color: rgba(var(--color-building), 0.9); }
20% { background-color: rgba(var(--color-building), 0.8); }
30% { background-color: rgba(var(--color-building), 0.7); }
40% { background-color: rgba(var(--color-building), 0.6); }
50% { background-color: rgba(var(--color-building), 0.5); }
60% { background-color: rgba(var(--color-building), 0.4); }
70% { background-color: rgba(var(--color-building), 0.3); }
80% { background-color: rgba(var(--color-building), 0.2); }
90% { background-color: rgba(var(--color-building), 0.1); }
100% { background-color: rgba(var(--color-building), 0.0); }
}
table, th, td {
padding: 5px;
}
td.package {
font-weight: bolder;
}
td.package-unknown {
background-color: rgba(var(--color-unknown), 1.0);
}
td.package-pending {
background-color: rgba(var(--color-pending), 1.0);
}
td.package-building {
background-color: rgba(var(--color-building), 1.0);
animation-name: blink-building;
animation-duration: 1s;
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-direction: alternate;
}
td.package-failed {
background-color: rgba(var(--color-failed), 1.0);
}
td.package-success {
background-color: rgba(var(--color-success), 1.0);
}
</style>
<script src="https://www.kryogenix.org/code/browser/sorttable/sorttable.js"></script>
</head> </head>
<body> <body>
<h1>ahriman {{ version|e }}</h1> <div class="root">
<h1>ahriman {{ version|e }}</h1>
<table class="sortable" id="builds"> {% include "search-line.jinja2" %}
<tr>
<th>package base</th>
<th>packages</th>
<th>version</th>
<th>architecture</th>
<th>timestamp</th>
<th>status</th>
</tr>
{% for package in packages %} <section class="element">
<tr> <table class="sortable search-table">
<td class="package"><a href="{{ package.web_url|e }}" title="{{ package.base|e }}">{{ package.base|e }}</a></td> <tr class="header">
<td>{{ package.packages|join("<br>"|safe) }}</td> <th>package base</th>
<td>{{ package.version|e }}</td> <th>packages</th>
<td>{{ architecture|e }}</td> <th>version</th>
<td>{{ package.timestamp|e }}</td> <th>architecture</th>
<td class="package-{{ package.status|e }}">{{ package.status|e }}</td> <th>last update</th>
</tr> <th>status</th>
{% endfor %} </tr>
</table>
{% for package in packages %}
<tr class="package">
<td class="include-search"><a href="{{ package.web_url|e }}" title="{{ package.base|e }}">{{ package.base|e }}</a></td>
<td class="include-search">{{ package.packages|join("<br>"|safe) }}</td>
<td>{{ package.version|e }}</td>
<td>{{ architecture|e }}</td>
<td>{{ package.timestamp|e }}</td>
<td class="package-{{ package.status|e }}">{{ package.status|e }}</td>
</tr>
{% endfor %}
</table>
</section>
</div>
</body> </body>
</html> </html>

View File

@ -2,31 +2,53 @@
<html lang="en"> <html lang="en">
<head> <head>
<title>{{ repository|e }}</title> <title>{{ repository|e }}</title>
{% include "style.jinja2" %}
{% include "sorttable.jinja2" %}
{% include "search.jinja2" %}
</head> </head>
<body> <body>
<h1>Archlinux custom repository</h1> <div class="root">
<h1>Archlinux user repository</h1>
{% if pgp_key is not none %} <section class="element">
<p>This repository is signed with <a href="http://keys.gnupg.net/pks/lookup?search=0x{{ pgp_key|e }}" title="key search">{{ pgp_key|e }}</a>.</p> {% if pgp_key is not none %}
{% endif %} <p>This repository is signed with <a href="http://keys.gnupg.net/pks/lookup?search=0x{{ pgp_key|e }}&fingerprint=on&op=index" title="key search">{{ pgp_key|e }}</a> by default.</p>
{% endif %}
<code> <code>
$ cat /etc/pacman.conf<br> $ cat /etc/pacman.conf<br>
[{{ repository|e }}]<br> [{{ repository|e }}]<br>
Server = {{ link_path|e }}<br> Server = {{ link_path|e }}<br>
SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Package{% if has_package_signed %}Required{% else %}Never{% endif %} TrustedOnly SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Package{% if has_package_signed %}Required{% else %}Never{% endif %} TrustedOnly
</code> </code>
</section>
<p>Packages:</p> {% include "search-line.jinja2" %}
<ul>
{% for package, package_url in packages.items() %}
<li><a href="{{ package_url|e }}" title="{{ package|e }}">{{ package|e }}</a></li>
{% endfor %}
</ul>
{% if homepage is not none %} <section class="element">
<footer><a href="{{ homepage|e }}" title="homepage">Homepage</a></footer> <table class="sortable search-table">
{% endif %} <tr class="header">
<th>package</th>
<th>version</th>
<th>architecture</th>
</tr>
{% for package in packages %}
<tr class="package">
<td class="include-search"><a href="{{ link_path|e }}/{{ package.filename|e }}" title="{{ package.name|e }}">{{ package.name|e }}</a></td>
<td>{{ package.version|e }}</td>
<td>{{ architecture|e }}</td>
</tr>
{% endfor %}
</table>
</section>
{% if homepage is not none %}
<footer><a href="{{ homepage|e }}" title="homepage">Homepage</a></footer>
{% endif %}
</div>
</body> </body>
</html> </html>

View File

@ -0,0 +1,3 @@
<section class="element">
<input type="search" id="search" onkeyup="searchInTable()" placeholder="search for package" title="search for package"/>
</section>

View File

@ -0,0 +1,25 @@
<script type="text/javascript">
function searchInTable() {
const input = document.getElementById("search");
const filter = input.value.toLowerCase();
const tables = document.getElementsByClassName("search-table");
for (let i = 0; i < tables.length; i++) {
const tr = tables[i].getElementsByTagName("tr");
// from 1 coz of header
for (let i = 1; i < tr.length; i++) {
let td = tr[i].getElementsByClassName("include-search");
let display = "none";
for (let j = 0; j < td.length; j++) {
if (td[j].tagName.toLowerCase() === "td") {
if (td[j].innerHTML.toLowerCase().indexOf(filter) > -1) {
display = "";
break;
}
}
}
tr[i].style.display = display;
}
}
}
</script>

View File

@ -0,0 +1 @@
<script src="https://www.kryogenix.org/code/browser/sorttable/sorttable.js"></script>

View File

@ -0,0 +1,74 @@
<style>
:root {
--color-building: 250, 255, 146;
--color-failed: 255, 94, 94;
--color-pending: 250, 255, 146;
--color-success: 121, 255, 94;
--color-unknown: 197, 197, 197;
}
@keyframes blink-building {
0% { background-color: rgba(var(--color-building), 1.0); }
10% { background-color: rgba(var(--color-building), 0.9); }
20% { background-color: rgba(var(--color-building), 0.8); }
30% { background-color: rgba(var(--color-building), 0.7); }
40% { background-color: rgba(var(--color-building), 0.6); }
50% { background-color: rgba(var(--color-building), 0.5); }
60% { background-color: rgba(var(--color-building), 0.4); }
70% { background-color: rgba(var(--color-building), 0.3); }
80% { background-color: rgba(var(--color-building), 0.2); }
90% { background-color: rgba(var(--color-building), 0.1); }
100% { background-color: rgba(var(--color-building), 0.0); }
}
div.root {
width: 70%;
padding: 15px 15% 0;
}
section.element {
width: 100%;
padding: 10px 0;
}
code, input, table {
width: inherit;
}
th, td {
padding: 5px;
}
tr.package:nth-child(odd) {
background-color: rgba(255, 255, 255, 1);
}
tr.package:nth-child(even) {
background-color: rgba(235, 235, 255, 1);
}
tr.header, tr.package:hover {
background-color: rgba(200, 200, 255, 1);
}
td.package-unknown {
background-color: rgba(var(--color-unknown), 1.0);
}
td.package-pending {
background-color: rgba(var(--color-pending), 1.0);
}
td.package-building {
background-color: rgba(var(--color-building), 1.0);
animation-name: blink-building;
animation-duration: 1s;
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-direction: alternate;
}
td.package-failed {
background-color: rgba(var(--color-failed), 1.0);
}
td.package-success {
background-color: rgba(var(--color-success), 1.0);
}
</style>

View File

@ -57,6 +57,10 @@ setup(
('share/ahriman', [ ('share/ahriman', [
'package/share/ahriman/build-status.jinja2', 'package/share/ahriman/build-status.jinja2',
'package/share/ahriman/repo-index.jinja2', 'package/share/ahriman/repo-index.jinja2',
'package/share/ahriman/search.jinja2',
'package/share/ahriman/search-line.jinja2',
'package/share/ahriman/sorttable.jinja2',
'package/share/ahriman/style.jinja2',
]), ]),
], ],

View File

@ -51,7 +51,7 @@ def sync(args: argparse.Namespace) -> None:
def update(args: argparse.Namespace) -> None: def update(args: argparse.Namespace) -> None:
app = Application.from_args(args) app = Application.from_args(args)
log_fn = lambda line: print(line) if args.dry_run else app.logger.info(line) log_fn = lambda line: print(line) if args.dry_run else app.logger.info(line)
packages = app.get_updates(args.no_aur, args.no_manual, args.no_vcs, log_fn) packages = app.get_updates(args.package, args.no_aur, args.no_manual, args.no_vcs, log_fn)
if args.dry_run: if args.dry_run:
return return
app.update(packages) app.update(packages)
@ -79,6 +79,7 @@ if __name__ == '__main__':
add_parser.set_defaults(fn=add) add_parser.set_defaults(fn=add)
check_parser = subparsers.add_parser('check', description='check for updates') check_parser = subparsers.add_parser('check', description='check for updates')
check_parser.add_argument('package', help='filter check by packages', nargs='*')
check_parser.set_defaults(fn=update, no_aur=False, no_manual=True, no_vcs=False, dry_run=True) check_parser.set_defaults(fn=update, no_aur=False, no_manual=True, no_vcs=False, dry_run=True)
rebuild_parser = subparsers.add_parser('rebuild', description='rebuild whole repository') rebuild_parser = subparsers.add_parser('rebuild', description='rebuild whole repository')
@ -97,6 +98,7 @@ if __name__ == '__main__':
sync_parser.set_defaults(fn=sync) sync_parser.set_defaults(fn=sync)
update_parser = subparsers.add_parser('update', description='run updates') update_parser = subparsers.add_parser('update', description='run updates')
update_parser.add_argument('package', help='filter check by packages', nargs='*')
update_parser.add_argument('--dry-run', help='just perform check for updates, same as check command', action='store_true') update_parser.add_argument('--dry-run', help='just perform check for updates, same as check command', action='store_true')
update_parser.add_argument('--no-aur', help='do not check for AUR updates', action='store_true') update_parser.add_argument('--no-aur', help='do not check for AUR updates', action='store_true')
update_parser.add_argument('--no-manual', help='do not include manual updates', action='store_true') update_parser.add_argument('--no-manual', help='do not include manual updates', action='store_true')

View File

@ -47,10 +47,10 @@ class Application:
return cls(args.architecture, config) return cls(args.architecture, config)
def _known_packages(self) -> Set[str]: def _known_packages(self) -> Set[str]:
known_packages = set() known_packages: Set[str] = set()
# local set # local set
for package in self.repository.packages(): for package in self.repository.packages():
known_packages.update(package.packages) known_packages.update(package.packages.keys())
known_packages.update(self.repository.pacman.all_packages()) known_packages.update(self.repository.pacman.all_packages())
return known_packages return known_packages
@ -58,12 +58,12 @@ class Application:
self.report() self.report()
self.sync() self.sync()
def get_updates(self, no_aur: bool, no_manual: bool, no_vcs: bool, def get_updates(self, filter_packages: List[str], no_aur: bool, no_manual: bool, no_vcs: bool,
log_fn: Callable[[str], None]) -> List[Package]: log_fn: Callable[[str], None]) -> List[Package]:
updates = [] updates = []
if not no_aur: if not no_aur:
updates.extend(self.repository.updates_aur(no_vcs)) updates.extend(self.repository.updates_aur(filter_packages, no_vcs))
if not no_manual: if not no_manual:
updates.extend(self.repository.updates_manual()) updates.extend(self.repository.updates_manual())
@ -114,13 +114,18 @@ class Application:
self.repository.process_sync(targets) self.repository.process_sync(targets)
def update(self, updates: Iterable[Package]) -> None: def update(self, updates: Iterable[Package]) -> None:
def process_single(portion: Iterable[Package]): def process_update(paths: Iterable[str]) -> None:
packages = self.repository.process_build(portion) self.repository.process_update(paths)
self.repository.process_update(packages)
self._finalize() self._finalize()
# process built packages
packages = self.repository.packages_built()
process_update(packages)
# process manual packages
tree = Tree() tree = Tree()
tree.load(updates) tree.load(updates)
for num, level in enumerate(tree.levels()): for num, level in enumerate(tree.levels()):
self.logger.info(f'processing level #{num} {[package.base for package in level]}') self.logger.info(f'processing level #{num} {[package.base for package in level]}')
process_single(level) packages = self.repository.process_build(level)
process_update(packages)

View File

@ -20,11 +20,12 @@
import jinja2 import jinja2
import os import os
from typing import Dict from typing import Dict, Iterable
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.report.report import Report from ahriman.core.report.report import Report
from ahriman.core.util import package_like from ahriman.core.util import package_like
from ahriman.models.package import Package
from ahriman.models.sign_settings import SignSettings from ahriman.models.sign_settings import SignSettings
@ -32,37 +33,42 @@ class HTML(Report):
def __init__(self, architecture: str, config: Configuration) -> None: def __init__(self, architecture: str, config: Configuration) -> None:
Report.__init__(self, architecture, config) Report.__init__(self, architecture, config)
self.architecture = architecture
section = config.get_section_name('html', architecture) section = config.get_section_name('html', architecture)
self.report_path = config.get(section, 'path') self.report_path = config.get(section, 'path')
self.link_path = config.get(section, 'link_path') self.link_path = config.get(section, 'link_path')
self.template_path = config.get(section, 'template_path') self.template_path = config.get(section, 'template_path')
# base template vars # base template vars
self.sign_targets = [SignSettings.from_option(opt) for opt in config.getlist('sign', 'target')]
self.pgp_key = config.get('sign', 'key', fallback=None)
self.homepage = config.get(section, 'homepage', fallback=None) self.homepage = config.get(section, 'homepage', fallback=None)
self.repository = config.get('repository', 'name') self.repository = config.get('repository', 'name')
def generate(self, path: str) -> None: sign_section = config.get_section_name('sign', architecture)
self.sign_targets = [SignSettings.from_option(opt) for opt in config.getlist(sign_section, 'target')]
self.pgp_key = config.get(sign_section, 'key') if self.sign_targets else None
def generate(self, packages: Iterable[Package]) -> None:
# idea comes from https://stackoverflow.com/a/38642558 # idea comes from https://stackoverflow.com/a/38642558
templates_dir, template_name = os.path.split(self.template_path) templates_dir, template_name = os.path.split(self.template_path)
loader = jinja2.FileSystemLoader(searchpath=templates_dir) loader = jinja2.FileSystemLoader(searchpath=templates_dir)
environment = jinja2.Environment(loader=loader) environment = jinja2.Environment(loader=loader)
template = environment.get_template(template_name) template = environment.get_template(template_name)
packages: Dict[str, str] = {} content = [
for fn in sorted(os.listdir(path)): {
if not package_like(fn): 'filename': filename,
continue 'name': package,
packages[fn] = f'{self.link_path}/{fn}' 'version': base.version
} for base in packages for package, filename in base.packages.items()
]
html = template.render( html = template.render(
architecture=self.architecture,
homepage=self.homepage, homepage=self.homepage,
link_path=self.link_path, link_path=self.link_path,
has_package_signed=SignSettings.SignPackages in self.sign_targets, has_package_signed=SignSettings.SignPackages in self.sign_targets,
has_repo_signed=SignSettings.SignRepository in self.sign_targets, has_repo_signed=SignSettings.SignRepository in self.sign_targets,
packages=packages, packages= sorted(content, key=lambda item: item['filename']),
pgp_key=self.pgp_key, pgp_key=self.pgp_key,
repository=self.repository) repository=self.repository)

View File

@ -19,8 +19,11 @@
# #
import logging import logging
from typing import Iterable
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import ReportFailed from ahriman.core.exceptions import ReportFailed
from ahriman.models.package import Package
from ahriman.models.report_settings import ReportSettings from ahriman.models.report_settings import ReportSettings
@ -32,7 +35,7 @@ class Report:
self.config = config self.config = config
@staticmethod @staticmethod
def run(architecture: str, config: Configuration, target: str, path: str) -> None: def run(architecture: str, config: Configuration, target: str, packages: Iterable[Package]) -> None:
provider = ReportSettings.from_option(target) provider = ReportSettings.from_option(target)
if provider == ReportSettings.HTML: if provider == ReportSettings.HTML:
from ahriman.core.report.html import HTML from ahriman.core.report.html import HTML
@ -41,10 +44,10 @@ class Report:
report = Report(architecture, config) report = Report(architecture, config)
try: try:
report.generate(path) report.generate(packages)
except Exception: except Exception:
report.logger.exception('report generation failed', exc_info=True) report.logger.exception('report generation failed', exc_info=True)
raise ReportFailed() raise ReportFailed()
def generate(self, path: str) -> None: def generate(self, packages: Iterable[Package]) -> None:
pass pass

View File

@ -63,8 +63,8 @@ class Repository:
shutil.rmtree(os.path.join(self.paths.manual, package)) shutil.rmtree(os.path.join(self.paths.manual, package))
def _clear_packages(self) -> None: def _clear_packages(self) -> None:
for package in os.listdir(self.paths.packages): for package in self.packages_built():
os.remove(os.path.join(self.paths.packages, package)) os.remove(package)
def packages(self) -> List[Package]: def packages(self) -> List[Package]:
result: Dict[str, Package] = {} result: Dict[str, Package] = {}
@ -80,6 +80,12 @@ class Repository:
continue continue
return list(result.values()) return list(result.values())
def packages_built(self) -> List[str]:
return [
os.path.join(self.paths.packages, fn)
for fn in os.listdir(self.paths.packages)
]
def process_build(self, updates: Iterable[Package]) -> List[str]: def process_build(self, updates: Iterable[Package]) -> List[str]:
def build_single(package: Package) -> None: def build_single(package: Package) -> None:
self.web.set_building(package.base) self.web.set_building(package.base)
@ -99,10 +105,7 @@ class Repository:
continue continue
self._clear_build() self._clear_build()
return [ return self.packages_built()
os.path.join(self.paths.packages, fn)
for fn in os.listdir(self.paths.packages)
]
def process_remove(self, packages: Iterable[str]) -> str: def process_remove(self, packages: Iterable[str]) -> str:
def remove_single(package: str) -> None: def remove_single(package: str) -> None:
@ -111,11 +114,12 @@ class Repository:
except Exception: except Exception:
self.logger.exception(f'could not remove {package}', exc_info=True) self.logger.exception(f'could not remove {package}', exc_info=True)
requested = set(packages)
for local in self.packages(): for local in self.packages():
if local.base in packages: if local.base in packages:
to_remove = local.packages to_remove = set(local.packages.keys())
elif local.packages.intersection(packages): elif requested.intersection(local.packages.keys()):
to_remove = local.packages.intersection(packages) to_remove = requested.intersection(local.packages.keys())
else: else:
to_remove = set() to_remove = set()
self.web.remove(local.base, to_remove) self.web.remove(local.base, to_remove)
@ -128,7 +132,7 @@ class Repository:
if targets is None: if targets is None:
targets = self.config.getlist('report', 'target') targets = self.config.getlist('report', 'target')
for target in targets: for target in targets:
Report.run(self.architecture, self.config, target, self.paths.repository) Report.run(self.architecture, self.config, target, self.packages())
def process_sync(self, targets: Optional[Iterable[str]]) -> None: def process_sync(self, targets: Optional[Iterable[str]]) -> None:
if targets is None: if targets is None:
@ -154,7 +158,7 @@ class Repository:
return self.repo.repo_path return self.repo.repo_path
def updates_aur(self, no_vcs: bool) -> List[Package]: def updates_aur(self, filter_packages: Iterable[str], no_vcs: bool) -> List[Package]:
result: List[Package] = [] result: List[Package] = []
build_section = self.config.get_section_name('build', self.architecture) build_section = self.config.get_section_name('build', self.architecture)
@ -165,6 +169,8 @@ class Repository:
continue continue
if local.is_vcs and no_vcs: if local.is_vcs and no_vcs:
continue continue
if filter_packages and local.base not in filter_packages:
continue
try: try:
remote = Package.load(local.base, self.pacman, self.aur_url) remote = Package.load(local.base, self.pacman, self.aur_url)

View File

@ -61,8 +61,8 @@ class Leaf:
:param packages: :param packages:
:return: true if any of packages is dependency of the leaf, false otherwise :return: true if any of packages is dependency of the leaf, false otherwise
''' '''
for package in packages: for leaf in packages:
if package.package.packages.intersection(self.dependencies): if self.dependencies.intersection(leaf.package.packages.keys()):
return False return False
return True return True

View File

@ -24,10 +24,9 @@ import os
import shutil import shutil
import tempfile import tempfile
from dataclasses import dataclass, field from dataclasses import dataclass
from pyalpm import Handle
from srcinfo.parse import parse_srcinfo from srcinfo.parse import parse_srcinfo
from typing import List, Set, Type from typing import Dict, List, Optional, Set, Type
from ahriman.core.alpm.pacman import Pacman from ahriman.core.alpm.pacman import Pacman
from ahriman.core.exceptions import InvalidPackageInfo from ahriman.core.exceptions import InvalidPackageInfo
@ -39,7 +38,7 @@ class Package:
base: str base: str
version: str version: str
aur_url: str aur_url: str
packages: Set[str] = field(default_factory=set) packages: Dict[str, str] # map of package name to archive name
@property @property
def git_url(self) -> str: def git_url(self) -> str:
@ -68,7 +67,7 @@ class Package:
try: try:
Task.fetch(clone_dir, self.git_url) Task.fetch(clone_dir, self.git_url)
# update pkgver first # update pkgver first
check_output('makepkg', '--nodeps', '--noprepare', '--nobuild', check_output('makepkg', '--nodeps', '--nobuild',
exception=None, cwd=clone_dir) exception=None, cwd=clone_dir)
# generate new .SRCINFO and put it to parser # generate new .SRCINFO and put it to parser
src_info_source = check_output('makepkg', '--printsrcinfo', src_info_source = check_output('makepkg', '--printsrcinfo',
@ -76,19 +75,19 @@ class Package:
src_info, errors = parse_srcinfo(src_info_source) src_info, errors = parse_srcinfo(src_info_source)
if errors: if errors:
raise InvalidPackageInfo(errors) raise InvalidPackageInfo(errors)
return f'{src_info["pkgver"]}-{src_info["pkgrel"]}' return self.full_version(src_info.get('epoch'), src_info['pkgver'], src_info['pkgrel'])
finally: finally:
shutil.rmtree(clone_dir, ignore_errors=True) shutil.rmtree(clone_dir, ignore_errors=True)
@classmethod @classmethod
def from_archive(cls: Type[Package], path: str, pacman: Pacman, aur_url: str) -> Package: def from_archive(cls: Type[Package], path: str, pacman: Pacman, aur_url: str) -> Package:
package = pacman.handle.load_pkg(path) package = pacman.handle.load_pkg(path)
return cls(package.base, package.version, aur_url, {package.name}) return cls(package.base, package.version, aur_url, {package.name: os.path.basename(path)})
@classmethod @classmethod
def from_aur(cls: Type[Package], name: str, aur_url: str)-> Package: def from_aur(cls: Type[Package], name: str, aur_url: str)-> Package:
package = aur.info(name) package = aur.info(name)
return cls(package.package_base, package.version, aur_url, {package.name}) return cls(package.package_base, package.version, aur_url, {package.name: ''})
@classmethod @classmethod
def from_build(cls: Type[Package], path: str, aur_url: str) -> Package: def from_build(cls: Type[Package], path: str, aur_url: str) -> Package:
@ -96,9 +95,10 @@ class Package:
src_info, errors = parse_srcinfo(fn.read()) src_info, errors = parse_srcinfo(fn.read())
if errors: if errors:
raise InvalidPackageInfo(errors) raise InvalidPackageInfo(errors)
packages = set(src_info['packages'].keys()) packages = {key: '' for key in src_info['packages'].keys()}
version = cls.full_version(src_info.get('epoch'), src_info['pkgver'], src_info['pkgrel'])
return cls(src_info['pkgbase'], f'{src_info["pkgver"]}-{src_info["pkgrel"]}', aur_url, packages) return cls(src_info['pkgbase'], version, aur_url, packages)
@staticmethod @staticmethod
def dependencies(path: str) -> Set[str]: def dependencies(path: str) -> Set[str]:
@ -115,6 +115,11 @@ class Package:
packages = set(src_info['packages'].keys()) packages = set(src_info['packages'].keys())
return set(depends + makedepends) - packages return set(depends + makedepends) - packages
@staticmethod
def full_version(epoch: Optional[str], pkgver: str, pkgrel: str) -> str:
prefix = f'{epoch}:' if epoch else ''
return f'{prefix}{pkgver}-{pkgrel}'
@staticmethod @staticmethod
def load(path: str, pacman: Pacman, aur_url: str) -> Package: def load(path: str, pacman: Pacman, aur_url: str) -> Package:
try: try:

View File

@ -17,4 +17,4 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
__version__ = '0.11.0' __version__ = '0.11.5'