music-assistant-server

3 KBPY
gen_requirements_all.py
3 KB91 lines • python
1"""Generate updated constraint and requirements files."""
2
3from __future__ import annotations
4
5import json
6import os
7import re
8import sys
9import tomllib
10from pathlib import Path
11
12PACKAGE_REGEX = re.compile(r"^(?:--.+\s)?([-_\.\w\d]+).*==.+$")
13GIT_REPO_REGEX = re.compile(r"^(git\+https:\/\/[-_\.\w\d\/]+[@-_\.\w\d\/]*)$")
14
15# ruff: noqa: T201
16
17
18def gather_core_requirements() -> list[str]:
19    """Gather core requirements out of pyproject.toml."""
20    with open("pyproject.toml", "rb") as fp:
21        data = tomllib.load(fp)
22    return data["project"]["dependencies"]
23
24
25def gather_requirements_from_manifests() -> list[str]:
26    """Gather all of the requirements from provider manifests."""
27    dependencies: list[str] = []
28    providers_path = "music_assistant/providers"
29    for dir_str in os.listdir(providers_path):  # noqa: PTH208, RUF100
30        dir_path = os.path.join(providers_path, dir_str)
31        if not os.path.isdir(dir_path):
32            continue
33        # get files in subdirectory
34        for file_str in os.listdir(dir_path):  # noqa: PTH208, RUF100
35            file_path = os.path.join(dir_path, file_str)
36            if not os.path.isfile(file_path):
37                continue
38            if file_str != "manifest.json":
39                continue
40
41            with open(file_path) as _file:
42                provider_manifest = json.loads(_file.read())
43                if "requirements" in provider_manifest:
44                    dependencies += provider_manifest["requirements"]
45    return dependencies
46
47
48def main() -> int:
49    """Run the script."""
50    if not os.path.isfile("requirements_all.txt"):
51        print("Run this from MA root dir")
52        return 1
53
54    core_reqs = gather_core_requirements()
55    extra_reqs = gather_requirements_from_manifests()
56
57    # use intermediate dict to detect duplicates
58    # TODO: compare versions and only store most recent
59    final_requirements: dict[str, str] = {}
60    for req_str in core_reqs + extra_reqs:
61        package_name = req_str
62        if match := PACKAGE_REGEX.search(req_str):
63            package_name = match.group(1).lower().replace("_", "-")
64        elif match := GIT_REPO_REGEX.search(req_str):
65            package_name = match.group(1)
66        elif package_name in final_requirements:
67            # duplicate package without version is safe to ignore
68            continue
69        else:
70            print(f"Found requirement without (exact) version specifier: {req_str}")
71            package_name = req_str
72
73        existing = final_requirements.get(package_name)
74        if existing:
75            print(f"WARNING: ignore duplicate package: {package_name} - existing: {existing}")
76            continue
77        final_requirements[package_name] = req_str
78
79    content = "# WARNING: this file is autogenerated!\n\n"
80    for req_key in sorted(final_requirements):
81        req_str = final_requirements[req_key]
82        content += f"{req_str}\n"
83    # Always use LF line endings for cross-platform compatibility
84    Path("requirements_all.txt").write_text(content, newline="\n")
85
86    return 0
87
88
89if __name__ == "__main__":
90    sys.exit(main())
91