Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ class IosFrameworkReleaser extends Releaser {
p.join(targetLibraryDirectory.path, 'ShorebirdFlutter.xcframework'),
);

// Generate a podspec so users can integrate via CocoaPods if preferred.
_writePodspec(targetLibraryDirectory);

return targetLibraryDirectory;
}

Expand Down Expand Up @@ -148,18 +151,44 @@ class IosFrameworkReleaser extends Releaser {
),
);

/// Writes a podspec that wraps the release xcframework output, enabling
/// CocoaPods-based integration as an alternative to manual Xcode embedding.
void _writePodspec(Directory releaseDir) {
final podspecPath = p.join(
releaseDir.path,
'ShorebirdFlutter.podspec',
);
File(podspecPath).writeAsStringSync('''
Pod::Spec.new do |s|
s.name = 'ShorebirdFlutter'
s.version = '0.0.1'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bdero's review bot:

💡 s.version = '0.0.1' is the same string for every release, so Podfile.lock and pod outdated will show stale info regardless of which Shorebird release the user is actually shipping. With :path => integration the version doesn't drive resolution, so nothing breaks — but plumbing through argResults['release-version'] (the same value getReleaseVersion returns) makes the lockfile actually reflect reality. One catch: Flutter's release-version can include +build suffixes, and CocoaPods' SemVer parser is stricter than pub's, so we may need to strip or transform.

s.summary = 'Shorebird Flutter framework for add-to-app integration.'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bdero's review bot:

🧹 s.summary could mention that the framework is a Shorebird-patched Flutter engine, not stock Flutter — that's the whole point. "Shorebird's patched Flutter engine for add-to-app iOS integration" or similar. Minor.

s.homepage = 'https://shorebird.dev'
s.license = { :type => 'BSD-3-Clause' }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bdero's review bot:

💡 s.license = { :type => 'BSD-3-Clause' } — not sure this is accurate for the bundled binaries. The Flutter engine portion is BSD-3-Clause, but Shorebird's modifications and proprietary patching code ship in ShorebirdFlutter.xcframework too, and I think those are under different terms. Worth a check with someone who has more licensing context — at minimum, { :file => 'LICENSE' } pointing to a real bundled file is more honest than asserting one license type for the whole thing.

s.author = 'Shorebird'
s.source = { :path => '.' }
s.platform = :ios, '12.0'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bdero's review bot:

💡 s.platform = :ios, '12.0' is hardcoded, but the actual minimum is whatever MinimumOSVersion ends up baked into App.xcframework and ShorebirdFlutter.xcframework — driven by Flutter's minimum, which has been creeping up. CocoaPods doesn't cross-check podspec platform against framework Info.plist, so a future Flutter min-version bump would silently let users link Shorebird into iOS 12 apps where the binaries actually require 13+. Worth either reading from the xcframework's Info.plist or pulling from a Shorebird-side known-minimum constant.

s.vendored_frameworks = 'App.xcframework', 'ShorebirdFlutter.xcframework'
end
''');
}

@override
String get postReleaseInstructions {
final relativeFrameworkDirectoryPath = p.relative(releaseDirectory.path);
return '''

Your next step is to add the .xcframework files found in the ${lightCyan.wrap(relativeFrameworkDirectoryPath)} directory to your iOS app.

To do this:
1. Add the relative path to the ${lightCyan.wrap(relativeFrameworkDirectoryPath)} directory to your app's Framework Search Paths in your Xcode build settings.
2. Embed the App.xcframework and ShorebirdFlutter.framework in your Xcode project.
${styleBold.wrap('Option A: CocoaPods')}
Add the following to your app's Podfile:
${lightCyan.wrap("pod 'ShorebirdFlutter', :path => '$relativeFrameworkDirectoryPath'")}
Then run ${lightCyan.wrap('pod install')}.

Instructions for these steps can be found at https://docs.flutter.dev/add-to-app/ios/project-setup#option-b---embed-frameworks-in-xcode.
${styleBold.wrap('Option B: Manual Xcode embedding')}
1. Add the relative path to the ${lightCyan.wrap(relativeFrameworkDirectoryPath)} directory to your app's Framework Search Paths in your Xcode build settings.
2. Embed the App.xcframework and ShorebirdFlutter.xcframework in your Xcode project.
Instructions: https://docs.flutter.dev/add-to-app/ios/project-setup#option-b---embed-frameworks-in-xcode
Comment on lines +183 to +191
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bdero's review bot:

🧹 If CocoaPods is the new recommended path, worth labeling it that way ("Option A: CocoaPods (recommended)") so users don't read Option A, glance at Option B, and feel like they need to pick. Otherwise both options read as equally weighted choices and we get more "which one should I use?" support questions.

''';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,24 @@ void main() {
verify(() => artifactBuilder.buildIosFramework(args: [])).called(1);
});

test('generates podspec in release directory', () async {
await runWithOverrides(iosFrameworkReleaser.buildReleaseArtifacts);

final podspecFile = File(
p.join(projectRoot.path, 'release', 'ShorebirdFlutter.podspec'),
);
expect(podspecFile.existsSync(), isTrue);
final content = podspecFile.readAsStringSync();
expect(content, contains("s.name = 'ShorebirdFlutter'"));
expect(
content,
contains(
"s.vendored_frameworks = 'App.xcframework', "
"'ShorebirdFlutter.xcframework'",
),
);
});
Comment on lines +482 to +498
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bdero's review bot:

💡 Test only asserts two contains checks. A heredoc typo (missing end, mismatched quotes) wouldn't be caught — pod install would discover it at runtime in production. A golden-file comparison against the full podspec content would lock down the template; it costs almost nothing and means future template edits show up clearly in diffs. Optional: shelling out to pod ipc spec would catch syntax errors but that adds a CI dependency we probably don't want.


group('when --obfuscate is passed', () {
setUp(() {
when(() => argResults['obfuscate']).thenReturn(true);
Expand Down Expand Up @@ -686,18 +704,22 @@ void main() {
final relativeFrameworkDirectoryPath = p.relative(
p.join(projectRoot.path, 'release'),
);
final instructions = runWithOverrides(
() => iosFrameworkReleaser.postReleaseInstructions,
);
expect(instructions, contains('Option A: CocoaPods'));
expect(instructions, contains('Option B: Manual Xcode embedding'));
expect(
runWithOverrides(() => iosFrameworkReleaser.postReleaseInstructions),
equals('''

Your next step is to add the .xcframework files found in the ${lightCyan.wrap(relativeFrameworkDirectoryPath)} directory to your iOS app.

To do this:
1. Add the relative path to the ${lightCyan.wrap(relativeFrameworkDirectoryPath)} directory to your app's Framework Search Paths in your Xcode build settings.
2. Embed the App.xcframework and ShorebirdFlutter.framework in your Xcode project.

Instructions for these steps can be found at https://docs.flutter.dev/add-to-app/ios/project-setup#option-b---embed-frameworks-in-xcode.
'''),
instructions,
contains(
"pod 'ShorebirdFlutter', :path => "
"'$relativeFrameworkDirectoryPath'",
),
);
expect(instructions, contains('pod install'));
expect(
instructions,
contains('App.xcframework and ShorebirdFlutter.xcframework'),
);
});
});
Expand Down