The problem
BridgeGenerator.write writes every file in plan() over whatever is already
there and removes nothing. With --force, regenerating an existing package
with a narrower spec therefore leaves the previous run's files in place, and
the result is a package that is neither of the two things it has been.
Reproduced on main:
# 1. Flutter flavour, both platforms
dart run bin/binary_sdk_bridge.dart --name acme_ads --org com.example \
--ios-framework AcmeSDK --android-aar AcmeSDK --out /tmp/gen
# 2. Change your mind: native flavour, iOS only
dart run bin/binary_sdk_bridge.dart --name acme_ads --org com.example \
--flavor native --ios-framework AcmeSDK --force --out /tmp/gen
find /tmp/gen/acme_ads -type f
Everything from step 1 survives step 2: pubspec.yaml, lib/, test/, the
whole android/ module, tool/fetch_android_sdk.sh, and
ios/acme_ads/Sources/acme_ads/AcmeAdsPlugin.swift.
Why it matters
The leftovers are not inert, they are actively misleading:
ios/acme_ads/Sources/acme_ads/AcmeAdsPlugin.swift imports Flutter and is
now orphaned — the regenerated native Package.swift declares no acme_ads
target at all, so that file is simply never compiled and never reported.
pubspec.yaml still declares pluginClass: AcmeAdsPlugin for Android, for a
module the current spec did not generate.
- Anyone reading the directory cannot tell which files the current spec
produced and which are debris. plan() knows; the filesystem does not.
--force currently reads as "overwrite", and users will reasonably assume it
means "make this directory match the command I just ran".
Suggested approach
A blanket deleteSync(recursive: true) before writing is not the fix —
Package.swift and build.gradle.kts both say "Safe to edit — it is not
regenerated", and people do edit them. Destroying hand-written vendor calls
in the bridge would be a far worse bug than the one being fixed.
Two shapes that respect that, either welcome:
- Record what was generated. Write a small manifest of the emitted
relative paths (e.g. tool/.binary_sdk_bridge_manifest). On a --force
run, delete the paths listed there that are no longer in plan() and leave
everything else strictly alone. Only files this tool created are ever
removed.
- Report, then opt in. Have
write() return (or --force print) the
orphaned paths, and add a separate --prune flag that removes them. Less
magic, and the user sees the list before anything is deleted.
Whichever you pick, mention in the PR what happens to a file the user edited
after it was generated — that is the interesting case.
Files involved
lib/src/generator.dart — write(), and possibly plan() if a manifest
file joins the plan
bin/binary_sdk_bridge.dart — the --force path and its output
lib/src/templates/script_templates.dart — gitignore(), if a manifest file
is added
test/generator_test.dart — the BridgeGenerator.write group
README.md — the --force row in the options table
How to verify
Add a test to the BridgeGenerator.write group that does exactly the two-step
reproduction above against Directory.systemTemp.createTempSync, and asserts
that after step 2 pubspec.yaml and android/build.gradle.kts are gone (or,
for approach 2, are reported as orphaned).
dart test
dart analyze --fatal-infos
dart format .
The problem
BridgeGenerator.writewrites every file inplan()over whatever is alreadythere and removes nothing. With
--force, regenerating an existing packagewith a narrower spec therefore leaves the previous run's files in place, and
the result is a package that is neither of the two things it has been.
Reproduced on
main:Everything from step 1 survives step 2:
pubspec.yaml,lib/,test/, thewhole
android/module,tool/fetch_android_sdk.sh, andios/acme_ads/Sources/acme_ads/AcmeAdsPlugin.swift.Why it matters
The leftovers are not inert, they are actively misleading:
ios/acme_ads/Sources/acme_ads/AcmeAdsPlugin.swiftimports Flutter and isnow orphaned — the regenerated native
Package.swiftdeclares noacme_adstarget at all, so that file is simply never compiled and never reported.
pubspec.yamlstill declarespluginClass: AcmeAdsPluginfor Android, for amodule the current spec did not generate.
produced and which are debris.
plan()knows; the filesystem does not.--forcecurrently reads as "overwrite", and users will reasonably assume itmeans "make this directory match the command I just ran".
Suggested approach
A blanket
deleteSync(recursive: true)before writing is not the fix —Package.swiftandbuild.gradle.ktsboth say "Safe to edit — it is notregenerated", and people do edit them. Destroying hand-written vendor calls
in the bridge would be a far worse bug than the one being fixed.
Two shapes that respect that, either welcome:
relative paths (e.g.
tool/.binary_sdk_bridge_manifest). On a--forcerun, delete the paths listed there that are no longer in
plan()and leaveeverything else strictly alone. Only files this tool created are ever
removed.
write()return (or--forceprint) theorphaned paths, and add a separate
--pruneflag that removes them. Lessmagic, and the user sees the list before anything is deleted.
Whichever you pick, mention in the PR what happens to a file the user edited
after it was generated — that is the interesting case.
Files involved
lib/src/generator.dart—write(), and possiblyplan()if a manifestfile joins the plan
bin/binary_sdk_bridge.dart— the--forcepath and its outputlib/src/templates/script_templates.dart—gitignore(), if a manifest fileis added
test/generator_test.dart— theBridgeGenerator.writegroupREADME.md— the--forcerow in the options tableHow to verify
Add a test to the
BridgeGenerator.writegroup that does exactly the two-stepreproduction above against
Directory.systemTemp.createTempSync, and assertsthat after step 2
pubspec.yamlandandroid/build.gradle.ktsare gone (or,for approach 2, are reported as orphaned).