From 313862433e9d0a5674fc309b1ab1f0997de3828a Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sat, 3 Oct 2026 18:51:14 +0200 Subject: [PATCH] feat: Read apt dependencies from [tool.frappe-cloud] [deploy.dependencies.apt] is not a valid pyproject.toml table, because PEP 518 reserves all top-level tables except [tool.*]. Also read [tool.frappe-cloud] apt-dependencies and keep the legacy key working. Remove duplicate package names. --- agent/builder.py | 11 +++++++---- agent/tests/test_builder.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/agent/builder.py b/agent/builder.py index 3ddca8ef8..85988c13e 100644 --- a/agent/builder.py +++ b/agent/builder.py @@ -257,14 +257,17 @@ def _parse_additional_packages(self) -> list[str]: packages = [] for app in pmf: pyproject = pmf[app]["pyproject"] or {} - deps = pyproject.get("deploy", {}).get("dependencies", {}) - pkgs = deps.get("apt", {}).get("packages", []) + # Legacy key, not valid per PEP 518 (only [tool.*] is open to tools) + legacy_pkgs = ( + pyproject.get("deploy", {}).get("dependencies", {}).get("apt", {}).get("packages", []) + ) + tool_pkgs = pyproject.get("tool", {}).get("frappe-cloud", {}).get("apt-dependencies", []) - for p in pkgs: + for p in [*legacy_pkgs, *tool_pkgs]: p = p.strip() packages.append(p) - return packages + return list(dict.fromkeys(packages)) def _inject_additional_packages(self, packages: list[str]): """This hack simply injects the additional packages discovered post cloning""" diff --git a/agent/tests/test_builder.py b/agent/tests/test_builder.py index 1e2786722..7daeaeffd 100644 --- a/agent/tests/test_builder.py +++ b/agent/tests/test_builder.py @@ -30,3 +30,33 @@ def test_check_version(self): for actual, expected, want in cases: with self.subTest(actual=actual, expected=expected): self.assertEqual(ValidationManager.check_version(actual, expected), want) + + +class TestParseAdditionalPackages(unittest.TestCase): + def test_reads_legacy_and_tool_frappe_cloud_keys(self): + import os + import tempfile + from types import SimpleNamespace + + from agent.builder import ContextManager + + pyproject = """ +[tool.frappe-cloud] +apt-dependencies = ["poppler-utils", "libmagic1"] + +[deploy.dependencies.apt] +packages = ["libmagic1", " ffmpeg "] +""" + with tempfile.TemporaryDirectory() as build_directory: + app_path = os.path.join(build_directory, "apps", "my_app") + os.makedirs(app_path) + with open(os.path.join(app_path, "pyproject.toml"), "w") as f: + f.write(pyproject) + + context = SimpleNamespace( + build_directory=build_directory, + clone_instructions=[{"app": "my_app"}], + ) + packages = ContextManager._parse_additional_packages(context) + + self.assertEqual(packages, ["libmagic1", "ffmpeg", "poppler-utils"])