chromium/chrome/installer/linux/rpm/update_package_provides.py

#!/usr/bin/env python3
# Copyright 2017 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import binascii
import gzip
import hashlib
import io
import json
import os
import sys
import urllib.request
import xml.etree.ElementTree as ET


LIBRARY_FILTER = {
    "ld-linux-x86-64.so",
    "libX11-xcb.so",
    "libX11.so",
    "libXcomposite.so",
    "libXcursor.so",
    "libXdamage.so",
    "libXext.so",
    "libXfixes.so",
    "libXi.so",
    "libXrandr.so",
    "libXrender.so",
    "libXss.so",
    "libXtst.so",
    "libasound.so",
    "libatk-1.0.so",
    "libatk-bridge-2.0.so",
    "libatspi.so.0",
    "libc.so",
    "libcairo.so",
    "libcups.so",
    "libdbus-1.so",
    "libdl.so",
    "libdrm.so.2",
    "libexpat.so",
    "libgbm.so.1",
    "libgcc_s.so",
    "libgdk-3.so",
    "libgio-2.0.so",
    "libglib-2.0.so",
    "libgmodule-2.0.so",
    "libgobject-2.0.so",
    "libm.so",
    "libnspr4.so",
    "libnss3.so",
    "libnssutil3.so",
    "libpango-1.0.so",
    "libpangocairo-1.0.so",
    "libpthread.so",
    "librt.so",
    "libsmime3.so",
    "libstdc++.so",
    "libudev.so",
    "libuuid.so",
    "libxcb-dri3.so.0",
    "libxcb.so",
    "libxkbcommon.so.0",
    "libxshmfence.so.1",
    "rtld(GNU_HASH)",
}

SUPPORTED_FEDORA_RELEASES = ["39", "40"]
SUPPORTED_OPENSUSE_LEAP_RELEASES = ["15.5"]

COMMON_NS = "http://linux.duke.edu/metadata/common"
RPM_NS = "http://linux.duke.edu/metadata/rpm"
REPO_NS = "http://linux.duke.edu/metadata/repo"

rpm_sources = {}
for version in SUPPORTED_FEDORA_RELEASES:
    rpm_sources["Fedora " + version] = [
        "https://download.fedoraproject.org/pub/fedora/linux/releases/%s/Everything/x86_64/os/"
        % version,
        # 'updates' must appear after 'releases' since its entries
        # overwrite the originals.
        "https://download.fedoraproject.org/pub/fedora/linux/updates/%s/Everything/x86_64/"
        % version,
    ]
for version in SUPPORTED_OPENSUSE_LEAP_RELEASES:
    rpm_sources["openSUSE Leap " + version] = [
        "https://download.opensuse.org/distribution/leap/%s/repo/oss/" %
        version,
        # 'update' must appear after 'distribution' since its entries
        # overwrite the originals.
        "https://download.opensuse.org/update/leap/%s/oss/" % version,
    ]

provides = {}
missing_any_library = False
for distro in rpm_sources:
    distro_provides = {}
    provided_prefixes = set()
    for source in rpm_sources[distro]:
        source = urllib.request.urlopen(source).geturl()

        response = urllib.request.urlopen(source + "repodata/repomd.xml")
        repomd = ET.fromstring(response.read())
        primary = (source +
                   repomd.find("./{%s}data[@type='primary']/{%s}location" %
                               (REPO_NS, REPO_NS)).attrib["href"])
        expected_checksum = repomd.find(
            "./{%s}data[@type='primary']/{%s}checksum[@type='sha256']" %
            (REPO_NS, REPO_NS)).text

        response = urllib.request.urlopen(primary)
        gz_data = response.read()

        sha = hashlib.sha256()
        sha.update(gz_data)
        actual_checksum = binascii.hexlify(sha.digest()).decode("ascii")
        assert expected_checksum == actual_checksum

        with gzip.open(io.BytesIO(gz_data), "rb") as f:
            contents = f.read().decode("utf-8")
        metadata = ET.fromstring(contents)
        for package in metadata.findall("./{%s}package" % COMMON_NS):
            if package.find("./{%s}arch" % COMMON_NS).text != "x86_64":
                continue
            package_name = package.find("./{%s}name" % COMMON_NS).text
            package_provides = []
            for entry in package.findall(
                    "./{%s}format/{%s}provides/{%s}entry" %
                (COMMON_NS, RPM_NS, RPM_NS)):
                name = entry.attrib["name"]
                for prefix in LIBRARY_FILTER:
                    if name.startswith(prefix):
                        package_provides.append(name)
                        provided_prefixes.add(prefix)
            distro_provides[package_name] = package_provides
    provides[distro] = sorted(
        list(
            set([
                package_provides for package in distro_provides
                for package_provides in distro_provides[package]
            ])))

    missing_libraries = LIBRARY_FILTER.difference(provided_prefixes)
    if missing_libraries:
        missing_any_library = True
        print(
            "Libraries are not available on %s: %s" %
            (distro, ", ".join(missing_libraries)),
            file=sys.stderr,
        )

if missing_any_library:
    sys.exit(1)

script_dir = os.path.dirname(os.path.realpath(__file__))
with open(os.path.join(script_dir, "dist_package_provides.json"), "w") as f:
    json.dump(provides, f, sort_keys=True, indent=4, separators=(",", ": "))
    f.write("\n")