-
Notifications
You must be signed in to change notification settings - Fork 93
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
common/Scripts: Add a tool to check appstream metainfo progress (#2981)
**Summary** Adds a script to `common` which generates a report of packages shipping .desktop files, and how many of those are missing appstream metadata. The script can also scan a single pspec.xml. In this usage, it returns 0 if the package is correct (either ships no .desktop file or both a .desktop and appstream metadata), and returns 1 if the package ships only a .desktop file. Fixes #2394 **Test Plan** Test both use cases of the script. The full report is `./check_appstream_progress.py ../../packages` (assuming you are in `common/Scripts`. An example of (at this time) packages to test the single-package use case are `vlc` (correct) and `puddletag` (incorrect). **Checklist** - [ ] Package was built and tested against unstable
- Loading branch information
Showing
1 changed file
with
190 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |