2021-04-28 07:00:31 -07:00
|
|
|
from collections import defaultdict
|
2023-02-12 05:06:13 -08:00
|
|
|
from datetime import timedelta, timezone
|
2014-02-22 11:38:59 -08:00
|
|
|
|
2023-11-02 04:52:30 -07:00
|
|
|
from django.db import connection
|
2014-02-22 11:38:59 -08:00
|
|
|
from django.db.models import F
|
|
|
|
from django.template.defaultfilters import filesizeformat
|
2021-04-28 07:41:08 -07:00
|
|
|
from django.utils.html import format_html
|
2023-11-02 04:52:30 -07:00
|
|
|
from django.utils.timezone import now
|
|
|
|
|
2020-05-30 08:14:46 -07:00
|
|
|
from main.models import Package, PackageFile, RebuilderdStatus
|
2017-05-24 11:15:12 -07:00
|
|
|
from packages.models import Depend, PackageRelation
|
2014-02-22 11:38:59 -08:00
|
|
|
|
2017-05-01 13:38:45 -07:00
|
|
|
from .models import DeveloperKey
|
2017-05-24 11:15:12 -07:00
|
|
|
|
2014-02-22 11:38:59 -08:00
|
|
|
|
2021-04-28 07:41:08 -07:00
|
|
|
# Helper object to be able to show links reports.
|
|
|
|
class Linkify:
|
|
|
|
def __init__(self, href, title, desc):
|
|
|
|
self.href = href
|
|
|
|
self.title = title
|
|
|
|
self.desc = desc
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
link = '<a href="%s" title="%s">%s</a>'
|
|
|
|
return format_html(link % (self.href, self.title, self.desc))
|
|
|
|
|
|
|
|
|
2021-04-28 08:01:39 -07:00
|
|
|
def linkify_non_reproducible_packages(statuses):
|
|
|
|
'''Adds diffoscope/log attribute to non reproducible packages'''
|
|
|
|
|
|
|
|
pkgs = []
|
|
|
|
for status in statuses:
|
|
|
|
pkg = status.pkg
|
|
|
|
|
|
|
|
# Diffoscope url
|
|
|
|
url = f'https://reproducible.archlinux.org/api/v0/builds/{status.build_id}/diffoscope'
|
|
|
|
pkg.diffoscope = Linkify(url, 'Diffoscope of package', 'diffoscope')
|
|
|
|
|
|
|
|
# Build log
|
|
|
|
url = f'https://reproducible.archlinux.org/api/v0/builds/{status.build_id}/log'
|
|
|
|
pkg.log = Linkify(url, 'Logs of package', 'log')
|
|
|
|
pkgs.append(pkg)
|
|
|
|
|
|
|
|
return pkgs
|
|
|
|
|
|
|
|
|
2014-02-22 11:38:59 -08:00
|
|
|
class DeveloperReport(object):
|
2017-05-24 11:15:12 -07:00
|
|
|
def __init__(self,
|
|
|
|
slug,
|
|
|
|
name,
|
|
|
|
desc,
|
|
|
|
packages_func,
|
|
|
|
names=None,
|
|
|
|
attrs=None,
|
|
|
|
personal=True):
|
2014-02-22 11:38:59 -08:00
|
|
|
self.slug = slug
|
|
|
|
self.name = name
|
|
|
|
self.description = desc
|
|
|
|
self.packages = packages_func
|
|
|
|
self.names = names
|
|
|
|
self.attrs = attrs
|
2014-02-22 11:48:52 -08:00
|
|
|
self.personal = personal
|
2014-02-22 11:38:59 -08:00
|
|
|
|
|
|
|
|
2017-05-01 13:38:23 -07:00
|
|
|
def old(packages):
|
2014-02-22 11:38:59 -08:00
|
|
|
cutoff = now() - timedelta(days=365 * 2)
|
2017-05-24 11:15:12 -07:00
|
|
|
return packages.filter(build_date__lt=cutoff).order_by('build_date')
|
2014-02-22 11:38:59 -08:00
|
|
|
|
|
|
|
|
2017-05-01 13:38:23 -07:00
|
|
|
def outofdate(packages):
|
2014-02-22 11:38:59 -08:00
|
|
|
cutoff = now() - timedelta(days=30)
|
2017-05-24 11:15:12 -07:00
|
|
|
return packages.filter(flag_date__lt=cutoff).order_by('flag_date')
|
2014-02-22 11:38:59 -08:00
|
|
|
|
|
|
|
|
2017-05-01 13:38:23 -07:00
|
|
|
def big(packages):
|
2014-02-22 11:38:59 -08:00
|
|
|
cutoff = 50 * 1024 * 1024
|
|
|
|
packages = packages.filter(
|
2017-05-24 11:15:12 -07:00
|
|
|
compressed_size__gte=cutoff).order_by('-compressed_size')
|
2014-02-22 11:38:59 -08:00
|
|
|
# Format the compressed and installed sizes with MB/GB/etc suffixes
|
|
|
|
for package in packages:
|
|
|
|
package.compressed_size_pretty = filesizeformat(
|
|
|
|
package.compressed_size)
|
2017-05-24 11:15:12 -07:00
|
|
|
package.installed_size_pretty = filesizeformat(package.installed_size)
|
2014-02-22 11:38:59 -08:00
|
|
|
return packages
|
|
|
|
|
|
|
|
|
2017-05-01 13:38:23 -07:00
|
|
|
def badcompression(packages):
|
2014-02-22 11:38:59 -08:00
|
|
|
cutoff = 0.90 * F('installed_size')
|
2017-05-24 11:15:12 -07:00
|
|
|
packages = packages.filter(
|
|
|
|
compressed_size__gt=25 * 1024,
|
|
|
|
installed_size__gt=25 * 1024,
|
|
|
|
compressed_size__gte=cutoff).order_by('-compressed_size')
|
2014-02-22 11:38:59 -08:00
|
|
|
|
|
|
|
# Format the compressed and installed sizes with MB/GB/etc suffixes
|
|
|
|
for package in packages:
|
|
|
|
package.compressed_size_pretty = filesizeformat(
|
|
|
|
package.compressed_size)
|
2017-05-24 11:15:12 -07:00
|
|
|
package.installed_size_pretty = filesizeformat(package.installed_size)
|
2014-02-22 11:38:59 -08:00
|
|
|
ratio = package.compressed_size / float(package.installed_size)
|
|
|
|
package.ratio = '%.3f' % ratio
|
|
|
|
package.compress_type = package.filename.split('.')[-1]
|
|
|
|
|
|
|
|
return packages
|
|
|
|
|
|
|
|
|
|
|
|
def uncompressed_man(packages, username):
|
|
|
|
# checking for all '.0'...'.9' + '.n' extensions
|
2017-05-24 11:15:12 -07:00
|
|
|
bad_files = PackageFile.objects.filter(
|
|
|
|
is_directory=False,
|
|
|
|
directory__contains='/man/',
|
|
|
|
filename__regex=r'\.[0-9n]').exclude(filename__endswith='.gz').exclude(
|
2014-02-22 11:38:59 -08:00
|
|
|
filename__endswith='.xz').exclude(
|
2017-05-24 11:15:12 -07:00
|
|
|
filename__endswith='.bz2').exclude(filename__endswith='.html')
|
2014-02-22 11:38:59 -08:00
|
|
|
if username:
|
|
|
|
pkg_ids = set(packages.values_list('id', flat=True))
|
|
|
|
bad_files = bad_files.filter(pkg__in=pkg_ids)
|
2017-05-24 11:15:12 -07:00
|
|
|
bad_files = bad_files.values_list('pkg_id',
|
|
|
|
flat=True).order_by().distinct()
|
2014-02-22 11:38:59 -08:00
|
|
|
return packages.filter(id__in=set(bad_files))
|
|
|
|
|
|
|
|
|
|
|
|
def uncompressed_info(packages, username):
|
|
|
|
# we don't worry about looking for '*.info-1', etc., given that an
|
|
|
|
# uncompressed root page probably exists in the package anyway
|
|
|
|
bad_files = PackageFile.objects.filter(is_directory=False,
|
2017-05-24 11:15:12 -07:00
|
|
|
directory__endswith='/info/',
|
|
|
|
filename__endswith='.info')
|
2014-02-22 11:38:59 -08:00
|
|
|
if username:
|
|
|
|
pkg_ids = set(packages.values_list('id', flat=True))
|
|
|
|
bad_files = bad_files.filter(pkg__in=pkg_ids)
|
2017-05-24 11:15:12 -07:00
|
|
|
bad_files = bad_files.values_list('pkg_id',
|
|
|
|
flat=True).order_by().distinct()
|
2014-02-22 11:38:59 -08:00
|
|
|
return packages.filter(id__in=set(bad_files))
|
|
|
|
|
|
|
|
|
2017-05-01 13:38:23 -07:00
|
|
|
def unneeded_orphans(packages):
|
2014-02-22 11:38:59 -08:00
|
|
|
owned = PackageRelation.objects.all().values('pkgbase')
|
|
|
|
required = Depend.objects.all().values('name')
|
|
|
|
# The two separate calls to exclude is required to do the right thing
|
2017-05-24 11:15:12 -07:00
|
|
|
return packages.exclude(pkgbase__in=owned).exclude(pkgname__in=required)
|
2014-02-22 11:38:59 -08:00
|
|
|
|
|
|
|
|
2017-05-01 13:38:23 -07:00
|
|
|
def mismatched_signature(packages):
|
2014-02-22 11:38:59 -08:00
|
|
|
filtered = []
|
|
|
|
packages = packages.select_related(
|
2017-05-24 11:15:12 -07:00
|
|
|
'arch', 'repo', 'packager').filter(signature_bytes__isnull=False)
|
|
|
|
known_keys = DeveloperKey.objects.select_related('owner').filter(
|
|
|
|
owner__isnull=False)
|
2014-02-22 11:38:59 -08:00
|
|
|
known_keys = {dk.key: dk for dk in known_keys}
|
|
|
|
for package in packages:
|
|
|
|
bad = False
|
|
|
|
sig = package.signature
|
|
|
|
dev_key = known_keys.get(sig.key_id, None)
|
|
|
|
if dev_key:
|
|
|
|
package.sig_by = dev_key.owner
|
|
|
|
if dev_key.owner_id != package.packager_id:
|
|
|
|
bad = True
|
|
|
|
else:
|
|
|
|
package.sig_by = sig.key_id
|
|
|
|
bad = True
|
|
|
|
|
|
|
|
if bad:
|
|
|
|
filtered.append(package)
|
|
|
|
return filtered
|
|
|
|
|
|
|
|
|
2017-05-01 13:38:23 -07:00
|
|
|
def signature_time(packages):
|
2014-02-22 11:56:51 -08:00
|
|
|
cutoff = timedelta(hours=24)
|
|
|
|
filtered = []
|
|
|
|
packages = packages.select_related(
|
2017-05-24 11:15:12 -07:00
|
|
|
'arch', 'repo', 'packager').filter(signature_bytes__isnull=False)
|
2014-02-22 11:56:51 -08:00
|
|
|
for package in packages:
|
|
|
|
sig = package.signature
|
2023-02-12 05:06:13 -08:00
|
|
|
sig_date = sig.creation_time.replace(tzinfo=timezone.utc)
|
2014-02-22 11:56:51 -08:00
|
|
|
package.sig_date = sig_date.date()
|
|
|
|
if sig_date > package.build_date + cutoff:
|
|
|
|
filtered.append(package)
|
|
|
|
|
|
|
|
return filtered
|
|
|
|
|
2020-05-30 08:14:46 -07:00
|
|
|
|
2019-05-17 13:43:00 -07:00
|
|
|
def non_existing_dependencies(packages):
|
|
|
|
cursor = connection.cursor()
|
|
|
|
query = """
|
|
|
|
select p.pkgname "pkgname", pd.name "dependency pkgname", depp.pkgname, provp.pkgname
|
|
|
|
from packages_depend pd
|
|
|
|
join packages p on p.id = pd.pkg_id
|
|
|
|
left join packages depp on depp.pkgname = pd.name
|
|
|
|
left join packages_provision depp_prov on depp_prov.name = pd.name
|
|
|
|
left join packages provp on provp.id = depp_prov.pkg_id;"""
|
|
|
|
|
|
|
|
packages = []
|
|
|
|
cursor.execute(query)
|
|
|
|
for row in cursor.fetchall():
|
|
|
|
pkgname, pdname, dep_pkgname, prov_pkgname = row
|
|
|
|
if not prov_pkgname and not dep_pkgname:
|
|
|
|
package = Package.objects.normal().filter(pkgname=pkgname).first()
|
|
|
|
package.nonexistingdep = pdname
|
|
|
|
packages.append(package)
|
|
|
|
|
|
|
|
return packages
|
2019-06-28 02:56:13 -07:00
|
|
|
|
2019-05-17 13:43:00 -07:00
|
|
|
|
2020-05-30 08:14:46 -07:00
|
|
|
def non_reproducible_packages(packages):
|
2021-04-28 07:41:08 -07:00
|
|
|
statuses = RebuilderdStatus.objects.select_related().filter(status=RebuilderdStatus.BAD, pkg__pkgname__in=packages.values('pkgname'))
|
2021-04-28 08:01:39 -07:00
|
|
|
return linkify_non_reproducible_packages(statuses)
|
2021-04-28 07:41:08 -07:00
|
|
|
|
|
|
|
|
2021-04-28 08:01:39 -07:00
|
|
|
def orphan_non_reproducible_packages(packages):
|
|
|
|
owned = PackageRelation.objects.all().values('pkgbase')
|
|
|
|
required = Depend.objects.all().values('name')
|
2021-04-28 07:41:08 -07:00
|
|
|
|
2021-04-28 08:01:39 -07:00
|
|
|
statuses = RebuilderdStatus.objects.select_related().filter(status=RebuilderdStatus.BAD)
|
|
|
|
orphans = packages.exclude(pkgbase__in=owned).exclude(pkgname__in=required)
|
|
|
|
statuses = statuses.filter(pkg__in=orphans)
|
|
|
|
return linkify_non_reproducible_packages(statuses)
|
2020-05-30 08:14:46 -07:00
|
|
|
|
2014-02-22 11:56:51 -08:00
|
|
|
|
2021-04-28 07:00:31 -07:00
|
|
|
def orphan_dependencies(packages):
|
|
|
|
packages_with_orphan_deps = []
|
|
|
|
required_mapping = defaultdict(list)
|
|
|
|
|
|
|
|
cursor = connection.cursor()
|
|
|
|
query = """
|
|
|
|
SELECT DISTINCT pp.pkgbase, ppr.user_id, child.pkgname
|
|
|
|
FROM packages_depend ppd JOIN packages pp ON ppd.pkg_id = pp.id
|
|
|
|
JOIN packages_packagerelation ppr ON pp.pkgbase = ppr.pkgbase
|
|
|
|
JOIN (SELECT DISTINCT cp.pkgname FROM packages cp LEFT JOIN packages_packagerelation pr ON cp.pkgbase = pr.pkgbase WHERE pr.id IS NULL) child ON ppd.name = child.pkgname
|
|
|
|
ORDER BY child.pkgname;
|
|
|
|
"""
|
|
|
|
cursor.execute(query)
|
|
|
|
|
|
|
|
for row in cursor.fetchall():
|
|
|
|
pkgname, _, orphan = row
|
|
|
|
required_mapping[pkgname].append(orphan)
|
|
|
|
packages_with_orphan_deps.append(pkgname)
|
|
|
|
|
|
|
|
pkgs = packages.filter(pkgname__in=packages_with_orphan_deps)
|
|
|
|
|
|
|
|
for pkg in pkgs:
|
|
|
|
# Templates take a string
|
|
|
|
pkg.orphandeps = ' '.join(required_mapping.get(pkg.pkgname, []))
|
|
|
|
|
|
|
|
return pkgs
|
|
|
|
|
|
|
|
|
2017-05-24 11:15:12 -07:00
|
|
|
REPORT_OLD = DeveloperReport(
|
|
|
|
'old', 'Old', 'Packages last built more than two years ago', old)
|
2014-02-22 11:38:59 -08:00
|
|
|
|
2017-05-24 11:15:12 -07:00
|
|
|
REPORT_OUTOFDATE = DeveloperReport(
|
|
|
|
'long-out-of-date', 'Long Out-of-date',
|
|
|
|
'Packages marked out-of-date more than 30 days ago', outofdate)
|
2014-02-22 11:38:59 -08:00
|
|
|
|
2017-05-24 11:15:12 -07:00
|
|
|
REPORT_BIG = DeveloperReport(
|
|
|
|
'big', 'Big', 'Packages with compressed size > 50 MiB', big,
|
|
|
|
['Compressed Size', 'Installed Size'],
|
|
|
|
['compressed_size_pretty', 'installed_size_pretty'])
|
2014-02-22 11:38:59 -08:00
|
|
|
|
2017-05-24 11:15:12 -07:00
|
|
|
REPORT_BADCOMPRESS = DeveloperReport(
|
|
|
|
'badcompression', 'Bad Compression',
|
|
|
|
'Packages > 25 KiB with a compression ratio < 10%', badcompression,
|
|
|
|
['Compressed Size', 'Installed Size', 'Ratio', 'Type'],
|
|
|
|
['compressed_size_pretty', 'installed_size_pretty', 'ratio',
|
|
|
|
'compress_type'])
|
2014-02-22 11:38:59 -08:00
|
|
|
|
|
|
|
REPORT_MAN = DeveloperReport('uncompressed-man', 'Uncompressed Manpages',
|
2017-05-24 11:15:12 -07:00
|
|
|
'Packages with uncompressed manpages',
|
|
|
|
uncompressed_man)
|
2014-02-22 11:38:59 -08:00
|
|
|
|
|
|
|
REPORT_INFO = DeveloperReport('uncompressed-info', 'Uncompressed Info Pages',
|
2017-05-24 11:15:12 -07:00
|
|
|
'Packages with uncompressed info pages',
|
|
|
|
uncompressed_info)
|
2014-02-22 11:38:59 -08:00
|
|
|
|
2017-05-24 11:15:12 -07:00
|
|
|
REPORT_ORPHANS = DeveloperReport(
|
|
|
|
'unneeded-orphans',
|
|
|
|
'Unneeded Orphans',
|
2020-10-03 21:59:41 -07:00
|
|
|
'Packages that have no maintainer and are not required by any other package in any repository',
|
2017-05-24 11:15:12 -07:00
|
|
|
unneeded_orphans,
|
|
|
|
personal=False)
|
2014-02-22 11:38:59 -08:00
|
|
|
|
2017-05-24 11:15:12 -07:00
|
|
|
REPORT_SIGNATURE = DeveloperReport(
|
|
|
|
'mismatched-signature', 'Mismatched Signatures',
|
|
|
|
'Packages where the signing key is unknown or signer != packager',
|
|
|
|
mismatched_signature, ['Signed By', 'Packager'], ['sig_by', 'packager'])
|
2014-02-22 11:56:51 -08:00
|
|
|
|
2017-05-24 11:15:12 -07:00
|
|
|
REPORT_SIG_TIME = DeveloperReport(
|
|
|
|
'signature-time', 'Signature Time',
|
2020-10-03 21:59:41 -07:00
|
|
|
'Packages where the signature timestamp is more than 24 hours after the build timestamp',
|
|
|
|
signature_time,
|
2017-05-24 11:15:12 -07:00
|
|
|
['Signature Date', 'Packager'], ['sig_date', 'packager'])
|
2014-02-22 11:38:59 -08:00
|
|
|
|
2019-05-17 13:43:00 -07:00
|
|
|
NON_EXISTING_DEPENDENCIES = DeveloperReport(
|
|
|
|
'non-existing-dependencies',
|
|
|
|
'Non existing dependencies',
|
|
|
|
'Packages that have dependencies that do not exists in the repository',
|
|
|
|
non_existing_dependencies,
|
|
|
|
['Non existing dependency'],
|
|
|
|
['nonexistingdep'],
|
|
|
|
personal=False)
|
2014-02-22 11:38:59 -08:00
|
|
|
|
2020-05-30 08:14:46 -07:00
|
|
|
REBUILDERD_PACKAGES = DeveloperReport(
|
|
|
|
'non-reproducible-packages',
|
|
|
|
'Non Reproducible package',
|
|
|
|
'Packages that are not reproducible on our reproducible.archlinux.org test environment',
|
2021-04-28 07:41:08 -07:00
|
|
|
non_reproducible_packages,
|
|
|
|
['diffoscope', 'log'],
|
|
|
|
['diffoscope', 'log'])
|
2020-05-30 08:14:46 -07:00
|
|
|
|
2021-04-28 08:01:39 -07:00
|
|
|
ORPHAN_REBUILDERD_PACKAGES = DeveloperReport(
|
|
|
|
'orphan-non-reproducible-packages',
|
|
|
|
'Orphan non Reproducible package',
|
|
|
|
'Orphan packages that are not reproducible on our reproducible.archlinux.org test environment',
|
|
|
|
orphan_non_reproducible_packages,
|
|
|
|
['diffoscope', 'log'],
|
|
|
|
['diffoscope', 'log'],
|
|
|
|
personal=False)
|
|
|
|
|
2021-04-28 07:00:31 -07:00
|
|
|
REPORT_REQUIRED_ORPHAN = DeveloperReport(
|
|
|
|
'required-orphan',
|
|
|
|
'Required orphan packages',
|
|
|
|
'Packages with orphan dependencies',
|
|
|
|
orphan_dependencies,
|
|
|
|
['Orphan dependencies'],
|
|
|
|
['orphandeps'])
|
|
|
|
|
2020-05-30 08:14:46 -07:00
|
|
|
|
2014-02-22 11:38:59 -08:00
|
|
|
def available_reports():
|
2017-05-24 11:15:12 -07:00
|
|
|
return (REPORT_OLD,
|
|
|
|
REPORT_OUTOFDATE,
|
|
|
|
REPORT_BIG,
|
|
|
|
REPORT_BADCOMPRESS,
|
|
|
|
REPORT_MAN,
|
|
|
|
REPORT_INFO,
|
|
|
|
REPORT_ORPHANS,
|
2021-04-28 07:00:31 -07:00
|
|
|
REPORT_REQUIRED_ORPHAN,
|
2017-05-24 11:15:12 -07:00
|
|
|
REPORT_SIGNATURE,
|
2019-05-17 13:43:00 -07:00
|
|
|
REPORT_SIG_TIME,
|
2020-05-30 08:14:46 -07:00
|
|
|
NON_EXISTING_DEPENDENCIES,
|
2021-04-28 08:01:39 -07:00
|
|
|
REBUILDERD_PACKAGES,
|
2022-09-23 00:31:20 -07:00
|
|
|
ORPHAN_REBUILDERD_PACKAGES)
|