diff --git a/pkgs/by-name/mu/music-assistant/dont-install-deps.patch b/pkgs/by-name/mu/music-assistant/dont-install-deps.patch new file mode 100644 index 000000000000..d8a420ae0d92 --- /dev/null +++ b/pkgs/by-name/mu/music-assistant/dont-install-deps.patch @@ -0,0 +1,36 @@ +diff --git a/music_assistant/server/helpers/util.py b/music_assistant/server/helpers/util.py +index 3d342b73..a395fb46 100644 +--- a/music_assistant/server/helpers/util.py ++++ b/music_assistant/server/helpers/util.py +@@ -117,30 +117,11 @@ async def load_provider_module(domain: str, requirements: list[str]) -> Provider + def _get_provider_module(domain: str) -> ProviderModuleType: + return importlib.import_module(f".{domain}", "music_assistant.server.providers") + +- # ensure module requirements are met +- for requirement in requirements: +- if "==" not in requirement: +- # we should really get rid of unpinned requirements +- continue +- package_name, version = requirement.split("==", 1) +- installed_version = await get_package_version(package_name) +- if installed_version == "0.0.0": +- # ignore editable installs +- continue +- if installed_version != version: +- await install_package(requirement) +- + # try to load the module + try: + return await asyncio.to_thread(_get_provider_module, domain) + except ImportError: +- # (re)install ALL requirements +- for requirement in requirements: +- await install_package(requirement) +- # try loading the provider again to be safe +- # this will fail if something else is wrong (as it should) +- return await asyncio.to_thread(_get_provider_module, domain) +- ++ raise RuntimeError(f"Missing dependencies for provider {domain}.") + + def create_tempfile(): + """Return a (named) temporary file.""" diff --git a/pkgs/by-name/mu/music-assistant/package.nix b/pkgs/by-name/mu/music-assistant/package.nix index a019ad7b22fa..ebbd057f138a 100644 --- a/pkgs/by-name/mu/music-assistant/package.nix +++ b/pkgs/by-name/mu/music-assistant/package.nix @@ -43,6 +43,9 @@ python.pkgs.buildPythonApplication rec { ffmpeg = "${lib.getBin ffmpeg-headless}/bin/ffmpeg"; ffprobe = "${lib.getBin ffmpeg-headless}/bin/ffprobe"; }) + + # Disable interactive dependency resolution, which clashes with the immutable Python environment + ./dont-install-deps.patch ]; postPatch = '' diff --git a/pkgs/by-name/mu/music-assistant/update-providers.py b/pkgs/by-name/mu/music-assistant/update-providers.py index 5016f918deba..d794719fea3c 100755 --- a/pkgs/by-name/mu/music-assistant/update-providers.py +++ b/pkgs/by-name/mu/music-assistant/update-providers.py @@ -1,5 +1,5 @@ #!/usr/bin/env nix-shell -#!nix-shell -i python3 -p "python3.withPackages (ps: with ps; [ jinja2 mashumaro orjson aiofiles packaging ])" -p pyright ruff isort +#!nix-shell -i python3 -p "python3.withPackages (ps: with ps; [ jinja2 mashumaro orjson aiofiles packaging ])" -p pyright ruff isort nixfmt-rfc-style import asyncio import json import os.path @@ -49,6 +49,7 @@ ROOT: Final = ( .strip() ) +PACKAGE_SET = "music-assistant.python.pkgs" PACKAGE_MAP = { "git+https://github.com/MarvinSchenkel/pytube.git": "pytube", } @@ -167,6 +168,11 @@ def resolve_package_attribute(package: str) -> str: return matches.pop() +async def get_package_version(package: str) -> str: + version = cast(str, await Nix.eval(f"{PACKAGE_SET}.{package}.version")) + return version + + @dataclass class Provider: domain: str @@ -180,7 +186,8 @@ class Provider: return hash(self.domain) -def resolve_providers(manifests) -> Set: +async def resolve_providers(manifests) -> Set: + errors = [] providers = set() for manifest in manifests: provider = Provider(manifest.domain) @@ -188,30 +195,44 @@ def resolve_providers(manifests) -> Set: # allow substituting requirement specifications that packaging cannot parse if requirement in PACKAGE_MAP: requirement = PACKAGE_MAP[requirement] + requirement = Requirement(requirement) try: - provider.available.append(resolve_package_attribute(requirement.name)) + attr = resolve_package_attribute(requirement.name) + provider.available.append(attr) except TooManyMatches as ex: print(ex, file=sys.stderr) provider.missing.append(requirement.name) + continue except NoMatch: provider.missing.append(requirement.name) + continue + + version = await get_package_version(attr) + if version not in requirement.specifier: + errors.append(f"{requirement} not satisifed by version {version}") providers.add(provider) + if errors: + print("\n - ", end="") + print("\n - ".join(errors)) return providers -def render(version: str, providers: Set): - path = os.path.join(ROOT, "pkgs/by-name/mu/music-assistant/providers.nix") +def render(outpath: str, version: str, providers: Set): env = Environment() template = env.from_string(TEMPLATE) - template.stream(version=version, providers=providers).dump(path) + template.stream(version=version, providers=providers).dump(outpath) async def main(): version: str = cast(str, await Nix.eval("music-assistant.version")) manifests = await get_provider_manifests(version) - providers = resolve_providers(manifests) - render(version, providers) + providers = await resolve_providers(manifests) + + outpath = os.path.join(ROOT, "pkgs/by-name/mu/music-assistant/providers.nix") + render(outpath, version, providers) + + run_sync(["nixfmt", outpath]) if __name__ == "__main__":