Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

common/Scripts: Add a tool to check appstream metainfo progress #2981

Merged
merged 1 commit into from
Jun 15, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 190 additions & 0 deletions common/Scripts/check_appstream_progress.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
#!/usr/bin/python3
import argparse
import os
import sys
import pathlib
from xml.etree import ElementTree

APPLICATIONS_DIRS = [
pathlib.Path("/usr/share/applications"),
]

APPSTREAM_DATA_DIRS = [
pathlib.Path("/usr/share/metainfo"),
pathlib.Path("/usr/share/appdata"),
]


def main():
parser = argparse.ArgumentParser(
prog="check_appstream_progress",
description="Scan either the whole Solus packages repo or a single pspec.xml "
"to check for .desktop files and appstream metadata.",
formatter_class=argparse.RawTextHelpFormatter,
)
parser.add_argument(
"--package-names-only",
action="store_true",
dest="package_names_only",
default=False,
help="Output only the names of packages which need appstream metainfo added to them",
)
parser.add_argument(
"packages_or_pspec_path",
action="store",
help="""Path to the Solus packages repo or pspec.xml file.\n
If a directory is provided, it will be scanned for pspec.xml files, and a report will be generated of the """
"""appstream data status of all discovered packages.\n
If a file is provided, it will be scanned as though it is a pspec.xml file, and the program will:
If the package would ship a .desktop file and no appstream metainfo, exit 1.
If the package would ship a .desktop file with correct metainfo, exit 0.
If the package would ship no .desktop file, exit 0.
Text explaining these results is also printed to stdout.""",
type=pathlib.Path,
)
args = parser.parse_args()
if args.packages_or_pspec_path.exists():
if os.path.isdir(args.packages_or_pspec_path):
if not args.package_names_only:
print("Directory detected, scanning whole repository...")
repo_info = check_whole_repo(pathlib.Path(sys.argv[1]))
if not args.package_names_only:
print(
f'Count of packages shipping .desktop file: {len(repo_info["packages_with_desktop_file"])}'
)
print(
f'Count of packages shipping appstream metadata: {len(repo_info["packages_with_appstream_file"])}'
)
print(
f'Count of packages which need appstream metadata: {len(repo_info["problematic_packages"])}'
)
print("Problematic Packages:")
for package in repo_info["problematic_packages"]:
print(f"{package}")
else:
print("File detected, parsing single pspec.xml...")
if args.package_names_only:
print(
"Warning: option --package-names-only has no effect when scanning a single pspec.xml file."
)
result = check_single_pspec(args.packages_or_pspec_path)
if not result:
print("Package has a .desktop file and no appstream metainfo.")
exit(1)
else:
print(
"If the package ships a .desktop file, it also ships appstream metadata. Good work!"
)
exit(0)
else:
print("Specified path does not exist.")
exit(1)


def _has_desktop_file(pspec_root: ElementTree.Element) -> bool:
"""
Checks for a .desktop file in one of the specified directories.

param pspec_root: The root element of a tree representing a pspec.xml file.
"""
has_desktop_file = False
for package in pspec_root.findall("Package"):
for path in package.find("Files").findall("Path"):
if path.text.endswith(
".desktop"
): # It's a .desktop file, but is it in the right place?
desktop_path = pathlib.Path(path.text)
if desktop_path.parent in APPLICATIONS_DIRS:
has_desktop_file = True
break # We found a .desktop file, stop iterating
if has_desktop_file:
break # We found a .desktop file, stop iterating
return has_desktop_file


def _has_appstream_file(pspec_root: ElementTree.Element) -> bool:
"""
Checks for an appstream metadata file in one of the specified directories.

param pspec_root: The root element of a tree representing a pspec.xml file.
"""
has_appstream_file = False
for package in pspec_root.findall("Package"):
for path in package.find("Files").findall("Path"):
if path.text.endswith("metainfo.xml") or path.text.endswith("appdata.xml"):
appstream_path = pathlib.Path(path.text)
if appstream_path.parent in APPSTREAM_DATA_DIRS:
has_appstream_file = True
break # We found appstream metainfo, stop iterating
if has_appstream_file:
break # We found appstream metainfo, stop iterating
return has_appstream_file


def _get_info_from_pspec(pspec_path: pathlib.Path) -> dict:
"""
Parses a pspec.xml file and returns a dictionary containing information about it.
Specifically, this function checks for appstream metadata files and .desktop files.

param pspec_path: A path to a pspec.xml file.
"""
out = {
"has_desktop_file": False,
"has_appstream_file": False,
}
pspec_file = open(pspec_path, "r")
pspec_tree = ElementTree.parse(pspec_file)
pspec_root = pspec_tree.getroot()
out["has_desktop_file"] = _has_desktop_file(pspec_root)
out["has_appstream_file"] = _has_appstream_file(pspec_root)
return out


def check_single_pspec(pspec_path: pathlib.Path) -> bool:
"""
Checks a pspec.xml file to make sure that if the package ships a GUI app, it also ships appstream metainfo.

param pspec_path: Path to a pspec.xml file
"""
correct = None
pspec_info = _get_info_from_pspec(pspec_path)
if pspec_info["has_desktop_file"]:
correct = False # Yer on thin ice, buddy
if pspec_info["has_appstream_file"]:
correct = True
return correct


def check_whole_repo(packages_path: pathlib.Path) -> dict:
"""
Checks the entire Solus packages repository worth of pspec.xml files for .desktop files and appstream metadata.
Returns a dictionary containing appstream status information for the whole repository.
"""
out = {
"packages_with_desktop_file": [],
"packages_with_appstream_file": [],
"problematic_packages": [],
}
for directory in os.walk(packages_path):
leaf_files = directory[-1]
for file in leaf_files:
if file.startswith("pspec"):
current_path = pathlib.Path(directory[0])
package_name = current_path.name
pspec_info = _get_info_from_pspec(
pathlib.Path.joinpath(current_path, file)
)
if pspec_info["has_desktop_file"]:
out["packages_with_desktop_file"].append(package_name)
if pspec_info["has_appstream_file"]:
out["packages_with_appstream_file"].append(package_name)
if (
pspec_info["has_desktop_file"]
and not pspec_info["has_appstream_file"]
):
out["problematic_packages"].append(package_name)
return out


if __name__ == "__main__":
main()
Loading