mirror of
https://github.com/getsolus/packages.git
synced 2025-04-25 20:30:37 +03:00
217 lines
7.8 KiB
Python
217 lines
7.8 KiB
Python
#!/usr/bin/env python3
|
|
# SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers
|
|
#
|
|
# SPDX-License-Identifier: MPL-2.0
|
|
|
|
import argparse
|
|
import atexit
|
|
import glob
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import unittest
|
|
from importlib.metadata import Distribution
|
|
|
|
from packaging.requirements import Requirement
|
|
from ruamel.yaml import YAML
|
|
from pisi.api import fetch
|
|
from pisi.package import Package
|
|
|
|
|
|
SOLUS_RECIPE_FILE = ["package.yml", "package.yaml"]
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("location", type=str, nargs='?', help="Location of .egg-info or .dist-info directory")
|
|
|
|
eopkg_already_exists = False
|
|
|
|
|
|
@staticmethod
|
|
def get_dependencies(dependencies: list, env=None) -> list:
|
|
sanitized = []
|
|
|
|
if dependencies is None:
|
|
return sanitized
|
|
|
|
for dep in dependencies:
|
|
req = Requirement(dep)
|
|
if not req.extras:
|
|
if req.marker:
|
|
mark = req.marker
|
|
if mark.evaluate(environment=env) is True:
|
|
sanitized.append(req.name)
|
|
continue
|
|
else:
|
|
sanitized.append(req.name)
|
|
return sanitized
|
|
|
|
|
|
def usage(msg=None, ex=1):
|
|
if msg:
|
|
print(msg, file=sys.stderr)
|
|
else:
|
|
parser.print_help()
|
|
sys.exit(ex)
|
|
|
|
|
|
def parse_recipe(path) -> tuple:
|
|
yaml = YAML()
|
|
with open(path, 'r') as file:
|
|
data = yaml.load(file)
|
|
|
|
name = data.get('name')
|
|
version = data.get('version')
|
|
release = data.get('release')
|
|
|
|
return f"{name}-{version}-{release}-1-x86_64.eopkg", name
|
|
|
|
|
|
def init_recipe_parse_path():
|
|
for i in SOLUS_RECIPE_FILE:
|
|
if os.path.exists(i):
|
|
eopkg_path, package_name = parse_recipe(i)
|
|
if not os.path.exists(eopkg_path):
|
|
fetch([package_name])
|
|
else:
|
|
global eopkg_already_exists
|
|
eopkg_already_exists = True
|
|
extract_eopkg(eopkg_path)
|
|
find_python_files("install")
|
|
|
|
|
|
def find_python_files(path):
|
|
target_dirs = [".egg-info", ".dist-info"]
|
|
|
|
for root, dirs, files in os.walk(path):
|
|
for dir_name in dirs:
|
|
for substring in target_dirs:
|
|
if substring in dir_name:
|
|
print_dependencies(os.path.join(root, dir_name))
|
|
|
|
|
|
def extract_eopkg(eopkg=str):
|
|
|
|
package = Package(eopkg)
|
|
|
|
if not os.path.exists("install"):
|
|
os.makedirs("install")
|
|
|
|
package.extract_pisi_files(".")
|
|
package.extract_dir("comar", ".")
|
|
if not os.path.exists(os.path.join(".", "install")):
|
|
os.makedirs(os.path.join(".", "install"))
|
|
package.extract_install(os.path.join(".", "install"))
|
|
if os.listdir("install") == []:
|
|
os.rmdir("install")
|
|
|
|
|
|
def init_location_path(path):
|
|
if not (os.path.exists(os.path.join(path, 'METADATA')) or os.path.exists(os.path.join(path, 'PKG-INFO'))):
|
|
usage("Unable to find PKG-INFO or METADATA files in specified directory")
|
|
print_dependencies(path)
|
|
|
|
|
|
def find_mismatched_elements(list1, list2):
|
|
normalized_list1 = {item.lower().removeprefix("python-").replace("-", "_") for item in list1}
|
|
normalized_list2 = {item.lower().replace("-", "_") for item in list2}
|
|
|
|
return normalized_list1 ^ normalized_list2
|
|
|
|
|
|
def check_against_rundeps(deps):
|
|
yaml = YAML()
|
|
with open("package.yml", "r") as file:
|
|
data = yaml.load(file)
|
|
rundeps = data.get("rundeps", [])
|
|
if rundeps:
|
|
missing_elements = find_mismatched_elements(rundeps, deps)
|
|
|
|
print("Required Deps:", deps)
|
|
print("Current Run Deps:", rundeps)
|
|
print("Mismatched dependencies:", missing_elements)
|
|
print("NOTE: The 'python-' prefix is removed and '-' '_' are equivalently compared.")
|
|
|
|
|
|
def print_dependencies(path):
|
|
dependencies = Distribution.at(path).requires
|
|
|
|
printed_deps = get_dependencies(dependencies)
|
|
|
|
if printed_deps:
|
|
check_against_rundeps(printed_deps)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
args = parser.parse_args()
|
|
|
|
if args.location and not os.path.exists(args.location):
|
|
usage()
|
|
|
|
if not any(os.path.exists(path) for path in SOLUS_RECIPE_FILE) and args.location is None:
|
|
usage(msg="Expects a package.yml in current directory or pass a .egg-info or .dist-info path")
|
|
|
|
if args.location is None:
|
|
init_recipe_parse_path()
|
|
else:
|
|
init_location_path(args.location)
|
|
|
|
|
|
# Cleanup on exit
|
|
@atexit.register
|
|
def cleanuponexit():
|
|
if eopkg_already_exists is False:
|
|
matched_files = [file_path for file_path in glob.glob("*.eopkg")]
|
|
for i in matched_files:
|
|
os.unlink(i)
|
|
if os.path.exists("install"):
|
|
shutil.rmtree("install")
|
|
if os.path.exists("files.xml"):
|
|
os.unlink("files.xml")
|
|
if os.path.exists("metadata.xml"):
|
|
os.unlink("metadata.xml")
|
|
|
|
|
|
class Tests(unittest.TestCase):
|
|
|
|
def test_basic(self):
|
|
self.assertEqual(get_dependencies(['six']), ['six'])
|
|
|
|
def test_empty_list(self):
|
|
self.assertEqual(get_dependencies([]), [])
|
|
|
|
def test_excludes_version(self):
|
|
self.assertEqual(get_dependencies(['six>=6.9']), ['six'])
|
|
|
|
def test_excludes_extras(self):
|
|
list1 = ['MarkupSafe>=2.0', 'Babel>=2.7; extra == "i18n"']
|
|
self.assertEqual(get_dependencies(list1), ['MarkupSafe'])
|
|
|
|
def test_excludes_extras_no_deps(self):
|
|
self.assertEqual(get_dependencies(['Babel>=2.7; extra == "i18n"', 'pytest; extra == "testing"']), [])
|
|
|
|
def test_excludes_extras_comprehensive(self):
|
|
list2 = ['MarkupSafe>=0.9.2', 'Babel; extra == "babel"',
|
|
'lingua; extra == "lingua"', 'pytest; extra == "testing"']
|
|
self.assertEqual(get_dependencies(list2), ['MarkupSafe'])
|
|
|
|
def test_ignore_satisfied_evaluate_markers(self):
|
|
env = {'python_version': '3.11'}
|
|
list3 = ["tomli>=1.2.2; python_version < '3.11'", 'pluggy>=1.0.0']
|
|
self.assertEqual(get_dependencies(list3, env), ['pluggy'])
|
|
|
|
def test_included_unsatisified_evaluate_markers(self):
|
|
env = {'python_version': '3.11'}
|
|
list4 = ['pluggy', "tomli>=1.2.2; python_version < '3.12'"]
|
|
self.assertEqual(get_dependencies(list4, env), ['pluggy', 'tomli'])
|
|
|
|
def test_comprehensive1(self):
|
|
env = {'python_version': '3.11'}
|
|
list5 = ['pytest; extra == "testing"', 'editables>=0.3', 'packaging>=21.3',
|
|
'pathspec>=0.10.1', 'pluggy>=1.0.0', "tomli>=1.2.2; python_version < '3.12'", 'trove-classifiers']
|
|
self.assertEqual(get_dependencies(list5, env), ['editables', 'packaging', 'pathspec',
|
|
'pluggy', 'tomli', 'trove-classifiers'])
|
|
|
|
def test_comprehensive2(self):
|
|
list6 = ["brotli; implementation_name == 'cpython'", "brotlicffi; implementation_name != 'cpython'", 'certifi', 'mutagen', 'pycryptodomex', 'requests<3,>=2.32.2', 'urllib3<3,>=1.26.17', 'websockets>=12.0', "build; extra == 'build'", "hatchling; extra == 'build'", "pip; extra == 'build'", "setuptools>=71.0.2; extra == 'build'", "wheel; extra == 'build'", "curl-cffi!=0.6.*,<0.8,>=0.5.10; (os_name != 'nt' and implementation_name == 'cpython') and extra == 'curl-cffi'", "curl-cffi==0.5.10; (os_name == 'nt' and implementation_name == 'cpython') and extra == 'curl-cffi'", "pre-commit; extra == 'dev'", "yt-dlp[static-analysis]; extra == 'dev'", "yt-dlp[test]; extra == 'dev'", "py2exe>=0.12; extra == 'py2exe'", "pyinstaller>=6.7.0; extra == 'pyinstaller'", "cffi; extra == 'secretstorage'", "secretstorage; extra == 'secretstorage'", "autopep8~=2.0; extra == 'static-analysis'", "ruff~=0.5.0; extra == 'static-analysis'", "pytest~=8.1; extra == 'test'"] # noqa: E501 There is no sane way to split all of this up into short enough lines
|
|
self.assertEqual(get_dependencies(list6), ['brotli', 'certifi', 'mutagen', 'pycryptodomex',
|
|
'requests', 'urllib3', 'websockets'])
|