Skip to content

--force overwrites but never prunes: regenerating a narrower spec leaves the previous run's files behind #2

Description

@saimskywalker

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:

  1. 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.
  2. 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.dartwrite(), 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.dartgitignore(), 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 .

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinghelp wantedExtra attention is needed

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions