From 793a4cd144f5769f03a049df30c155bf991f6a44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chojnacki?= Date: Fri, 31 Jul 2026 15:51:38 +0200 Subject: [PATCH] fix(flutter): recover path dependency locations from pubspec.yaml `flutter pub deps --json` reports path dependencies as source: "path" but omits the description carrying their location. The generated package_config therefore resolved an empty path for them, and the packages were silently dropped -- any workspace using a path dependency (vendored packages, a repo with several local packages, generated API clients) built without them and failed later on unresolved imports. Recover the location from the pubspec that declares the dependency, and use it only as a fallback when the JSON has no path of its own, so nothing changes for the cases that already worked. The lookup is a small indentation-aware scan rather than a YAML parse, since these scripts run with whatever bare python3 is on the host and cannot assume PyYAML. Applied at all three sites that resolve a path dependency: both copies of the package_config generator and the pubspec.lock synthesis. --- flutter/private/flutter_actions.bzl | 66 +++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/flutter/private/flutter_actions.bzl b/flutter/private/flutter_actions.bzl index 8addd25..4b3fd8c 100644 --- a/flutter/private/flutter_actions.bzl +++ b/flutter/private/flutter_actions.bzl @@ -255,6 +255,36 @@ def add_package(name, root_path): pkg["languageVersion"] = _package_language_version(root_path) packages.append(pkg) +def _path_deps_from_pubspec(root_path): + # `flutter pub deps --json` reports source == "path" but omits where the + # package lives, so recover the location from the pubspec declaring it. + locations = dict() + pubspec = os.path.join(root_path, "pubspec.yaml") + if not os.path.exists(pubspec): + return locations + + in_deps = False + current = None + with open(pubspec, "r", encoding="utf-8") as fh: + for line in fh: + if not line.strip() or line.lstrip().startswith("#"): + continue + indent = len(line) - len(line.lstrip()) + stripped = line.strip() + if indent == 0: + in_deps = stripped.startswith("dependencies:") or stripped.startswith("dev_dependencies:") + current = None + elif not in_deps: + continue + elif indent <= 2: + current = stripped[:-1] if stripped.endswith(":") else None + elif current and stripped.startswith("path:"): + locations[current] = stripped.split(":", 1)[1].strip().strip('"').strip("'") + return locations + + +path_dep_locations = _path_deps_from_pubspec(workspace_root) + for entry in data.get("packages", []): name = entry.get("name") source = entry.get("source") @@ -281,6 +311,8 @@ for entry in data.get("packages", []): path_value = description elif isinstance(description, dict): path_value = description.get("path") or "" + if not path_value: + path_value = path_dep_locations.get(name) or "" if path_value: add_package(name, os.path.abspath(os.path.join(workspace_root, path_value))) @@ -1138,6 +1170,36 @@ def add_package(name, root_path): pkg["languageVersion"] = _package_language_version(root_path) packages.append(pkg) +def _path_deps_from_pubspec(root_path): + # `flutter pub deps --json` reports source == "path" but omits where the + # package lives, so recover the location from the pubspec declaring it. + locations = dict() + pubspec = os.path.join(root_path, "pubspec.yaml") + if not os.path.exists(pubspec): + return locations + + in_deps = False + current = None + with open(pubspec, "r", encoding="utf-8") as fh: + for line in fh: + if not line.strip() or line.lstrip().startswith("#"): + continue + indent = len(line) - len(line.lstrip()) + stripped = line.strip() + if indent == 0: + in_deps = stripped.startswith("dependencies:") or stripped.startswith("dev_dependencies:") + current = None + elif not in_deps: + continue + elif indent <= 2: + current = stripped[:-1] if stripped.endswith(":") else None + elif current and stripped.startswith("path:"): + locations[current] = stripped.split(":", 1)[1].strip().strip('"').strip("'") + return locations + + +path_dep_locations = _path_deps_from_pubspec(workspace_root) + for entry in data.get("packages", []): name = entry.get("name") source = entry.get("source") @@ -1164,6 +1226,8 @@ for entry in data.get("packages", []): path_value = description elif isinstance(description, dict): path_value = description.get("path") or "" + if not path_value: + path_value = path_dep_locations.get(name) or "" if path_value: add_package(name, os.path.abspath(os.path.join(workspace_root, path_value))) @@ -1210,6 +1274,8 @@ if not os.path.exists(lock_path): path_value = description elif isinstance(description, dict): path_value = description.get("path") or "" + if not path_value: + path_value = path_dep_locations.get(name) or "" lines.append(" source: path") lines.append(" description:") lines.append(' path: "{{}}"'.format(path_value))