maplibre / maplibre/maplibre-react-native
post_install adds MapLibre SPM dependency to every user_target, crashing unrelated App Extensions (e.g. WidgetKit)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 661
- Forks
- 124
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 30
Description
## Summary
`MapLibreReactNative.podspec`'s `$MLRN.post_install` loops `installer.aggregate_targets` and, for **every** `user_target` it finds, unconditionally adds the MapLibre Swift Package dependency — with no check for whether that target actually depends on `MapLibreReactNative`. In a project that has a second native target integrated with its own CocoaPods aggregate target in the same `Podfile` (a WidgetKit / Notification Service / any App Extension target that pods something unrelated to MapLibre), that target gets the MapLibre SPM product linked into it too. Since nothing embeds the actual `MapLibre.framework` into that extension's bundle, the extension crashes on launch:
```
Library not loaded: @rpath/MapLibre.framework/MapLibre
Reason: tried: ... (no such file), ...
```
For a WidgetKit extension specifically, this crash happens before the extension can report any widget kinds to `chronod`/WidgetKit, so the app's widgets never show up in the widget gallery at all — with no build error, since each target compiles independently and Xcode only fails at **runtime**, when the extension process tries to `dyld`-load a framework that was never embedded there.
## Where
https://github.com/maplibre/maplibre-react-native/blob/main/MapLibreReactNative.podspec
```ruby
def $MLRN.post_install(installer)
spm_spec = $MLRN_SPM_SPEC
project = installer.pods_project
mlrn_target = project.targets.find { |t| t.name == "MapLibreReactNative" }
self._add_spm_to_target(
project,
mlrn_target,
spm_spec[:url],
spm_spec[:requirement],
spm_spec[:product_name]
)
installer.aggregate_targets.group_by(&:user_project).each do |project, targets|
targets.each do |target|
target.user_targets.each do |user_target|
self._add_spm_to_target(
project,
user_target,
spm_spec[:url],
spm_spec[:requirement],
spm_spec[:product_name]
)
phase_name = "[MapLibre React Native] Remove MapLibre.xcframework-ios.signature"
unless user_target.shell_script_build_phases.any? { |p| p.name == phase_name }
phase = user_target.new_shell_script_build_phase(phase_name)
phase.shell_script = 'rm -rf "$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature"'
phase.always_out_of_date = "1"
end
end
end
end
end
```
The second loop (`installer.aggregate_targets.group_by(&:user_project)...`) walks **every** aggregate target in the whole `Pods` install — not just the one that actually pods `MapLibreReactNative` — and calls `_add_spm_to_target` + adds the signature-removal build phase unconditionally on every `user_target` it finds.
## Repro
1. A React Native / Expo app using `@maplibre/maplibre-react-native` in the main app target.
2. Add a second native target with its own CocoaPods integration in the same `Podfile`, e.g.:
```ruby
target 'MyApp' do
# ... maplibre-react-native autolinked here via use_native_modules!
end
target 'MyAppWidget' do
pod 'SomeUnrelatedPod'
end
```
(This is exactly what Expo's Voltra widget plugin (`@use-voltra/ios-client`) generates — a widget extension target with its own CocoaPods target that only pods `VoltraWidget`, nothing MapLibre-related.)
3. Run `pod install`.
4. Inspect `MyAppWidget`'s Xcode target: it now has a Swift Package dependency on `MapLibre` (from `maplibre-gl-native-distribution`) and a `[MapLibre React Native] Remove MapLibre.xcframework-ios.signature` build phase, despite never referencing MapLibre.
5. Build and run the extension (or check `codesign`/the built `.appex`'s Mach-O load commands): the extension binary references `@rpath/MapLibre.framework/MapLibre`, but the framework is never embedded in that target's bundle, so the extension crashes on launch with `Library not loaded: @rpath/MapLibre.framework/MapLibre`.
## Expected behavior
Only targets that actually depend on `MapLibreReactNative` (i.e., targets whose aggregate target includes the `MapLibreReactNative` pod) should get the SPM dependency and the signature-removal build phase. Unrelated targets (extensions, etc.) sharing the same `Podfile`/workspace should be left alone.
## Suggested fix
Skip `user_target`s whose aggregate target doesn't actually include `MapLibreReactNative`, e.g.:
```ruby
installer.aggregate_targets.group_by(&:user_project).each do |project, targets|
targets.each do |target|
next unless target.pod_targets.map(&:pod_name).include?("MapLibreReactNative")
target.user_targets.each do |user_target|
# ... existing logic unchanged
end
end
end
```
Happy to open a PR with this change (or an equivalent guard) if that's welcome.
## Environment
- `@maplibre/maplibre-react-native`: 11.3.10 (bug present here too — confirmed against the current `main` podspec logic, not just an older version)
- Expo SDK 57, React Native 0.86
- The second target in our case is a WidgetKit extension generated by Expo's Voltra plugin (`@use-voltra/ios-client`), but the bug isn't specific to Voltra — any second CocoaPods-integrated target in the same workspace is affected.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in MapLibreReactNative.podspec at `$MLRN.post_install`, especially the loop over `installer.aggregate_targets`. Run `pod install` with a MapLibre app target and an unrelated extension target, then verify that only aggregates containing `MapLibreReactNative` receive the SPM dependency and signature-removal phase; the extension should no longer reference or load MapLibre.framework.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ios, react-native, ruby
- Domain
- build-system, mobile
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 84/100