1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
#!/usr/bin/env python3
import asyncio
import argparse
import heapq
import logging
from collections.abc import Coroutine
from pathlib import Path
from typing import Optional
import aiohttp
import aiofiles
from AURUtils.srcinfo import SRCINFO
from AURUtils.version import vercmp
MAX_WORKERS = 16
async def check_pkg(session: aiohttp.ClientSession, num: int, pkg: str) -> tuple[int, bool]:
logging.info(f'Checking {pkg}')
ret = await session.head(f'https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h={pkg}')
return num, ret.status == 200
async def check_aur_update(
session: aiohttp.ClientSession,
item: Path, dry_run: bool,
parse_pkgbuild: Optional[Path] = None,
check_deps: bool = True
) -> None:
if item.name.startswith('.') or not item.is_dir():
return
pkgbuild_file = item.joinpath('PKGBUILD')
if not pkgbuild_file.exists():
logging.warning(f'PKGBUILD not in {str(item)}, SKIP!')
return
srcinfo_file = item.joinpath('.SRCINFO')
if not srcinfo_file.exists():
if parse_pkgbuild.exists():
logging.warning(f'.SRCINFO not in {str(item)}, try to parse')
proc = await asyncio.create_subprocess_exec(
f'{str(parse_pkgbuild.resolve())}',
f'{str(pkgbuild_file)}',
stdout=asyncio.subprocess.PIPE,
)
srcinfo = SRCINFO.parse((await proc.stdout.read()).decode())
logging.info(f'extract .SRCINFO from {srcinfo.pkgbase}')
else:
logging.warning(f'.SRCINFO not in {str(item)}, no tool to parse')
return
else:
async with aiofiles.open(srcinfo_file, 'r') as f:
srcinfo = SRCINFO.parse(await f.read())
version = srcinfo.get_version()
async with session.get(f'https://aur.archlinux.org/cgit/aur.git/plain/.SRCINFO?h={srcinfo.pkgbase}') as resp:
if resp.status != 200:
logging.info(f'{item.name} not register in AUR')
return
new_srcinfo = SRCINFO.parse(await resp.text())
new_version = new_srcinfo.get_version()
result = vercmp(new_version, version)
if result == 0:
return
elif result < 0:
logging.warning(f'Warning: {item.name}({version}) is newer than AUR')
else:
if item.joinpath('.git').exists():
if not dry_run:
await asyncio.create_subprocess_exec('git', '-C', f'{str(item)}', 'pull', 'origin', 'master')
logging.info(f'Upgrade {item.name} from {version} to {new_version}')
else:
logging.info(f'Found update {item.name}({new_version}) (local: {version})')
if check_deps:
deps = [*new_srcinfo.makedepends, *new_srcinfo.depends]
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(10)) as session:
tasks = [asyncio.create_task(check_pkg(session, n, pkg)) for n, pkg in enumerate(deps)]
yay_dep_nums = []
for task in asyncio.as_completed(tasks):
n, r = await task
if r:
heapq.heappush(yay_dep_nums, n)
yaydeps_dir = item.parent.joinpath('.yaydeps')
if len(yay_dep_nums) > 0:
async with aiofiles.open(yaydeps_dir.joinpath(item.stem), 'w', encoding='utf-8') as f:
await f.write(
'\n'.join([*(deps[heapq.heappop(yay_dep_nums)] for _ in range(len(yay_dep_nums))), '']))
async def with_sem(sem: asyncio.Semaphore, coro: Coroutine):
async with sem:
return await coro
async def main() -> int:
parser = argparse.ArgumentParser(description='aur-check-update.py')
parser.add_argument('--dry-run', dest='dry', help='dry run', action='store_true')
parser.add_argument('--check-deps', dest='deps', help='check deps', action='store_true')
parser.add_argument('--parse-pkgbuild', dest='script', metavar='SCRIPT',
help='a script to parse pkgbuild',
action='store')
parser.add_argument('--miax-workers', dest='workers', metavar='MAX_WORKERS',
help=f'aur check max workers, default: {MAX_WORKERS}',
default=MAX_WORKERS, action='store', type=int)
parser.add_argument(dest='repo_dir', metavar='REPO_DIR', help='repository directory', nargs='?')
args = parser.parse_args()
dry_run = args.dry
check_deps = args.deps
sem = asyncio.Semaphore(args.workers)
if args.repo_dir is None:
logging.error('repository directory not found')
return 1
script_file = Path(args.script)
if args.script is None or not (script_file.is_file() and script_file.exists()):
logging.info(f'Parse script {str(script_file)+" " if args.script is None else ""} not found')
script_file = None
repo_dir = Path(args.repo_dir)
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(10)) as session:
tasks = [asyncio.create_task(
with_sem(sem, check_aur_update(
session=session,
item=item,
dry_run=dry_run,
parse_pkgbuild=script_file,
check_deps=check_deps
))
) for item in repo_dir.iterdir()]
await asyncio.gather(*tasks)
return 0
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(funcName)s - %(lineno)d - %(message)s')
asyncio.run(main())
|