From 5b36e847939255acb37df2771f00b3da6454380c Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Mon, 2 Nov 2026 10:32:47 +0100 Subject: [PATCH] repo_contract: skip Flutter plugins A plugin has android/ but no launcher activity. Also rename the sickdays desktop entry to the one-word form the label rule requires; that red gate blocked every pubspec commit. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WzxdWY1k4muZfdVJfb7YGJ --- repo_contract/launcher.py | 39 +++++++++++++++++++++------- repo_contract/tests/test_launcher.py | 14 ++++++++++ sickdays/sickdays.desktop | 2 +- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/repo_contract/launcher.py b/repo_contract/launcher.py index 8493dfb..609066f 100644 --- a/repo_contract/launcher.py +++ b/repo_contract/launcher.py @@ -1,7 +1,7 @@ """Launcher rules for every Flutter app in a repo (kuhy, 2026-10-23). Applies to each tracked `pubspec.yaml` whose `dependencies:` include the -Flutter SDK and that has an `android/` directory beside it, at the repo root +Flutter SDK (and that is not a `flutter: plugin:`) and that has an `android/` directory beside it, at the repo root or nested (screen-locker's workout_app): 1. the launcher name (`launcher_label.py`); @@ -47,22 +47,36 @@ def _tracked(repo: Path, *patterns: str) -> list[PurePosixPath] | None: return [PurePosixPath(p) for p in done.stdout.split("\0") if p] -def _needs_flutter(pubspec: Path) -> bool: - """Whether the top-level `dependencies:` block pulls in the Flutter SDK.""" +def _block(pubspec: Path, key: str) -> list[str]: + """Code lines (comments stripped) indented under top-level `key:`.""" try: lines = pubspec.read_text(encoding="utf-8").splitlines() except (OSError, UnicodeDecodeError): - return False - inside = False + return [] + inside, found = False, [] for line in lines: code = line.split("#", 1)[0].rstrip() # comments never end a block if not code.strip(): continue if not code[0].isspace(): - inside = code == "dependencies:" - elif inside and _SDK.match(code): - return True - return False + inside = code == f"{key}:" + elif inside: + found.append(code) + return found + + +def _is_plugin(pubspec: Path) -> bool: + """Whether the top-level `flutter:` block declares a `plugin:`. + + A plugin keeps an `android/` directory but ships no launcher activity, so + the app rules (label, variants, icon) have nothing to check in it. + """ + return any(code.strip() == "plugin:" for code in _block(pubspec, "flutter")) + + +def _needs_flutter(pubspec: Path) -> bool: + """Whether the top-level `dependencies:` block pulls in the Flutter SDK.""" + return any(_SDK.match(code) for code in _block(pubspec, "dependencies")) def flutter_apps(repo: Path) -> list[Path]: @@ -74,7 +88,12 @@ def flutter_apps(repo: Path) -> list[Path]: if PRUNE.intersection(rel.parts): continue app = repo / rel.parent - if (app / "android").is_dir() and _needs_flutter(app / "pubspec.yaml"): + pubspec = app / "pubspec.yaml" + if ( + (app / "android").is_dir() + and _needs_flutter(pubspec) + and not _is_plugin(pubspec) + ): apps.append(app) return apps diff --git a/repo_contract/tests/test_launcher.py b/repo_contract/tests/test_launcher.py index 20cb120..c7a26a5 100644 --- a/repo_contract/tests/test_launcher.py +++ b/repo_contract/tests/test_launcher.py @@ -13,6 +13,11 @@ from .flutter_app import flutter_app, track from .pngkit import write_icon ICON = "assets/icon/icon_foreground.png" +PUBSPEC_PLUGIN = ( + "dependencies:\n flutter:\n sdk: flutter\n" + "flutter:\n plugin:\n platforms:\n android:\n" + " pluginClass: P\n" +) DESKTOP = "[Desktop Entry]\nType=Application\nName={name}\nName[pl]=Zadania pl\n" @@ -61,6 +66,15 @@ def test_not_flutter_or_no_android_skipped(repo: Path) -> None: assert launcher.flutter_apps(repo) == [] +def test_flutter_plugin_is_not_an_app(repo: Path) -> None: + """A plugin has an android/ dir but no launcher activity to check.""" + body = PUBSPEC_PLUGIN + write(repo, "plug/pubspec.yaml", body) + write(repo, "plug/android/x", "") + track(repo) + assert launcher.flutter_apps(repo) == [] + + def test_pubspec_comments_do_not_hide_an_app(repo: Path) -> None: body = "dependencies: # exact pins\n# column-0 note\n\n flutter:\n sdk: flutter # sdk\n" write(repo, "a/pubspec.yaml", body) diff --git a/sickdays/sickdays.desktop b/sickdays/sickdays.desktop index a20f9b8..b61b894 100644 --- a/sickdays/sickdays.desktop +++ b/sickdays/sickdays.desktop @@ -1,6 +1,6 @@ [Desktop Entry] Type=Application -Name=Sick days +Name=Sickdays Comment=Mark sick days and choose which lockers still apply Exec=sickdays gui Icon=face-sick