Skip to content

Commit 2e229be

Browse files
authored
Native assets: package in framework on iOS and MacOS (#140907)
Packages the native assets for iOS and MacOS in frameworks. Issue: * flutter/flutter#140544 * flutter/flutter#129757 ## Details * [x] This packages dylibs from the native assets feature in frameworks. It packages every dylib in a separate framework. * [x] The dylib name is updated to use `@rpath` instead of `@executable_path`. * [x] The dylibs for flutter-tester are no longer modified to change the install name. (Previously it was wrongly updating the install name to the location the dylib would have once deployed in an app.) * [x] Use symlinking on MacOS.
1 parent 77c3807 commit 2e229be

File tree

9 files changed

+479
-107
lines changed

9 files changed

+479
-107
lines changed

dev/devicelab/bin/tasks/module_test_ios.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -250,9 +250,7 @@ dependencies:
250250
checkDirectoryNotExists(path.join(ephemeralIOSHostApp.path, 'Frameworks', '$dartPluginName.framework'));
251251

252252
// Native assets embedded, no embedded framework.
253-
const String libFfiPackageDylib = 'lib$ffiPackageName.dylib';
254-
checkFileExists(path.join(ephemeralIOSHostApp.path, 'Frameworks', libFfiPackageDylib));
255-
checkDirectoryNotExists(path.join(ephemeralIOSHostApp.path, 'Frameworks', '$ffiPackageName.framework'));
253+
checkFileExists(path.join(ephemeralIOSHostApp.path, 'Frameworks', '$ffiPackageName.framework', ffiPackageName));
256254

257255
section('Clean and pub get module');
258256

@@ -385,7 +383,8 @@ end
385383

386384
checkFileExists(path.join(
387385
hostFrameworksDirectory,
388-
libFfiPackageDylib,
386+
'$ffiPackageName.framework',
387+
ffiPackageName,
389388
));
390389

391390
section('Check the NOTICE file is correct');
@@ -491,7 +490,8 @@ end
491490
checkFileExists(path.join(
492491
archivedAppPath,
493492
'Frameworks',
494-
libFfiPackageDylib,
493+
'$ffiPackageName.framework',
494+
ffiPackageName,
495495
));
496496

497497
// The host app example builds plugins statically, url_launcher_ios.framework

packages/flutter_tools/lib/src/ios/native_assets.dart

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ Future<List<Uri>> buildNativeAssetsIOS({
106106
ensureNoLinkModeStatic(nativeAssets);
107107
globals.logger.printTrace('Building native assets for $targets done.');
108108
final Map<AssetPath, List<Asset>> fatAssetTargetLocations = _fatAssetTargetLocations(nativeAssets);
109-
await copyNativeAssetsMacOSHost(
109+
await _copyNativeAssetsIOS(
110110
buildUri,
111111
fatAssetTargetLocations,
112112
codesignIdentity,
@@ -145,21 +145,25 @@ Target _getNativeTarget(DarwinArch darwinArch) {
145145
}
146146

147147
Map<AssetPath, List<Asset>> _fatAssetTargetLocations(List<Asset> nativeAssets) {
148+
final Set<String> alreadyTakenNames = <String>{};
148149
final Map<AssetPath, List<Asset>> result = <AssetPath, List<Asset>>{};
149150
for (final Asset asset in nativeAssets) {
150-
final AssetPath path = _targetLocationIOS(asset).path;
151+
final AssetPath path = _targetLocationIOS(asset, alreadyTakenNames).path;
151152
result[path] ??= <Asset>[];
152153
result[path]!.add(asset);
153154
}
154155
return result;
155156
}
156157

157-
Map<Asset, Asset> _assetTargetLocations(List<Asset> nativeAssets) => <Asset, Asset>{
158-
for (final Asset asset in nativeAssets)
159-
asset: _targetLocationIOS(asset),
160-
};
158+
Map<Asset, Asset> _assetTargetLocations(List<Asset> nativeAssets) {
159+
final Set<String> alreadyTakenNames = <String>{};
160+
return <Asset, Asset>{
161+
for (final Asset asset in nativeAssets)
162+
asset: _targetLocationIOS(asset, alreadyTakenNames),
163+
};
164+
}
161165

162-
Asset _targetLocationIOS(Asset asset) {
166+
Asset _targetLocationIOS(Asset asset, Set<String> alreadyTakenNames) {
163167
final AssetPath path = asset.path;
164168
switch (path) {
165169
case AssetSystemPath _:
@@ -168,7 +172,52 @@ Asset _targetLocationIOS(Asset asset) {
168172
return asset;
169173
case AssetAbsolutePath _:
170174
final String fileName = path.uri.pathSegments.last;
171-
return asset.copyWith(path: AssetAbsolutePath(Uri(path: fileName)));
175+
return asset.copyWith(
176+
path: AssetAbsolutePath(frameworkUri(fileName, alreadyTakenNames)),
177+
);
178+
}
179+
throw Exception(
180+
'Unsupported asset path type ${path.runtimeType} in asset $asset');
181+
}
182+
183+
/// Copies native assets into a framework per dynamic library.
184+
///
185+
/// For `flutter run -release` a multi-architecture solution is needed. So,
186+
/// `lipo` is used to combine all target architectures into a single file.
187+
///
188+
/// The install name is set so that it matches what the place it will
189+
/// be bundled in the final app.
190+
///
191+
/// Code signing is also done here, so that it doesn't have to be done in
192+
/// in xcode_backend.dart.
193+
Future<void> _copyNativeAssetsIOS(
194+
Uri buildUri,
195+
Map<AssetPath, List<Asset>> assetTargetLocations,
196+
String? codesignIdentity,
197+
BuildMode buildMode,
198+
FileSystem fileSystem,
199+
) async {
200+
if (assetTargetLocations.isNotEmpty) {
201+
globals.logger
202+
.printTrace('Copying native assets to ${buildUri.toFilePath()}.');
203+
for (final MapEntry<AssetPath, List<Asset>> assetMapping
204+
in assetTargetLocations.entries) {
205+
final Uri target = (assetMapping.key as AssetAbsolutePath).uri;
206+
final List<Uri> sources = <Uri>[
207+
for (final Asset source in assetMapping.value)
208+
(source.path as AssetAbsolutePath).uri
209+
];
210+
final Uri targetUri = buildUri.resolveUri(target);
211+
final File dylibFile = fileSystem.file(targetUri);
212+
final Directory frameworkDir = dylibFile.parent;
213+
if (!await frameworkDir.exists()) {
214+
await frameworkDir.create(recursive: true);
215+
}
216+
await lipoDylibs(dylibFile, sources);
217+
await setInstallNameDylib(dylibFile);
218+
await createInfoPlist(targetUri.pathSegments.last, frameworkDir);
219+
await codesignDylib(codesignIdentity, buildMode, frameworkDir);
220+
}
221+
globals.logger.printTrace('Copying native assets done.');
172222
}
173-
throw Exception('Unsupported asset path type ${path.runtimeType} in asset $asset');
174223
}

packages/flutter_tools/lib/src/macos/native_assets.dart

Lines changed: 153 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,23 @@ Future<(Uri? nativeAssetsYaml, List<Uri> dependencies)> buildNativeAssetsMacOS({
108108
final Uri? absolutePath = flutterTester ? buildUri : null;
109109
final Map<Asset, Asset> assetTargetLocations = _assetTargetLocations(nativeAssets, absolutePath);
110110
final Map<AssetPath, List<Asset>> fatAssetTargetLocations = _fatAssetTargetLocations(nativeAssets, absolutePath);
111-
await copyNativeAssetsMacOSHost(buildUri, fatAssetTargetLocations, codesignIdentity, buildMode, fileSystem);
111+
if (flutterTester) {
112+
await _copyNativeAssetsMacOSFlutterTester(
113+
buildUri,
114+
fatAssetTargetLocations,
115+
codesignIdentity,
116+
buildMode,
117+
fileSystem,
118+
);
119+
} else {
120+
await _copyNativeAssetsMacOS(
121+
buildUri,
122+
fatAssetTargetLocations,
123+
codesignIdentity,
124+
buildMode,
125+
fileSystem,
126+
);
127+
}
112128
final Uri nativeAssetsUri = await writeNativeAssetsYaml(assetTargetLocations.values, yamlParentDirectory ?? buildUri, fileSystem);
113129
return (nativeAssetsUri, dependencies.toList());
114130
}
@@ -125,22 +141,40 @@ Target _getNativeTarget(DarwinArch darwinArch) {
125141
}
126142
}
127143

128-
Map<AssetPath, List<Asset>> _fatAssetTargetLocations(List<Asset> nativeAssets, Uri? absolutePath) {
144+
Map<AssetPath, List<Asset>> _fatAssetTargetLocations(
145+
List<Asset> nativeAssets,
146+
Uri? absolutePath,
147+
) {
148+
final Set<String> alreadyTakenNames = <String>{};
129149
final Map<AssetPath, List<Asset>> result = <AssetPath, List<Asset>>{};
130150
for (final Asset asset in nativeAssets) {
131-
final AssetPath path = _targetLocationMacOS(asset, absolutePath).path;
151+
final AssetPath path = _targetLocationMacOS(
152+
asset,
153+
absolutePath,
154+
alreadyTakenNames,
155+
).path;
132156
result[path] ??= <Asset>[];
133157
result[path]!.add(asset);
134158
}
135159
return result;
136160
}
137161

138-
Map<Asset, Asset> _assetTargetLocations(List<Asset> nativeAssets, Uri? absolutePath) => <Asset, Asset>{
139-
for (final Asset asset in nativeAssets)
140-
asset: _targetLocationMacOS(asset, absolutePath),
141-
};
162+
Map<Asset, Asset> _assetTargetLocations(
163+
List<Asset> nativeAssets,
164+
Uri? absolutePath,
165+
) {
166+
final Set<String> alreadyTakenNames = <String>{};
167+
return <Asset, Asset>{
168+
for (final Asset asset in nativeAssets)
169+
asset: _targetLocationMacOS(asset, absolutePath, alreadyTakenNames),
170+
};
171+
}
142172

143-
Asset _targetLocationMacOS(Asset asset, Uri? absolutePath) {
173+
Asset _targetLocationMacOS(
174+
Asset asset,
175+
Uri? absolutePath,
176+
Set<String> alreadyTakenNames,
177+
) {
144178
final AssetPath path = asset.path;
145179
switch (path) {
146180
case AssetSystemPath _:
@@ -157,9 +191,119 @@ Asset _targetLocationMacOS(Asset asset, Uri? absolutePath) {
157191
// Flutter Desktop needs "absolute" paths inside the app.
158192
// "relative" in the context of native assets would be relative to the
159193
// kernel or aot snapshot.
160-
uri = Uri(path: fileName);
194+
uri = frameworkUri(fileName, alreadyTakenNames);
195+
161196
}
162197
return asset.copyWith(path: AssetAbsolutePath(uri));
163198
}
164199
throw Exception('Unsupported asset path type ${path.runtimeType} in asset $asset');
165200
}
201+
202+
/// Copies native assets into a framework per dynamic library.
203+
///
204+
/// The framework contains symlinks according to
205+
/// https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/FrameworkAnatomy.html
206+
///
207+
/// For `flutter run -release` a multi-architecture solution is needed. So,
208+
/// `lipo` is used to combine all target architectures into a single file.
209+
///
210+
/// The install name is set so that it matches what the place it will
211+
/// be bundled in the final app.
212+
///
213+
/// Code signing is also done here, so that it doesn't have to be done in
214+
/// in macos_assemble.sh.
215+
Future<void> _copyNativeAssetsMacOS(
216+
Uri buildUri,
217+
Map<AssetPath, List<Asset>> assetTargetLocations,
218+
String? codesignIdentity,
219+
BuildMode buildMode,
220+
FileSystem fileSystem,
221+
) async {
222+
if (assetTargetLocations.isNotEmpty) {
223+
globals.logger.printTrace('Copying native assets to ${buildUri.toFilePath()}.');
224+
for (final MapEntry<AssetPath, List<Asset>> assetMapping in assetTargetLocations.entries) {
225+
final Uri target = (assetMapping.key as AssetAbsolutePath).uri;
226+
final List<Uri> sources = <Uri>[
227+
for (final Asset source in assetMapping.value)
228+
(source.path as AssetAbsolutePath).uri,
229+
];
230+
final Uri targetUri = buildUri.resolveUri(target);
231+
final String name = targetUri.pathSegments.last;
232+
final Directory frameworkDir = fileSystem.file(targetUri).parent;
233+
if (await frameworkDir.exists()) {
234+
await frameworkDir.delete(recursive: true);
235+
}
236+
// MyFramework.framework/ frameworkDir
237+
// MyFramework -> Versions/Current/MyFramework dylibLink
238+
// Resources -> Versions/Current/Resources resourcesLink
239+
// Versions/ versionsDir
240+
// A/ versionADir
241+
// MyFramework dylibFile
242+
// Resources/ resourcesDir
243+
// Info.plist
244+
// Current -> A currentLink
245+
final Directory versionsDir = frameworkDir.childDirectory('Versions');
246+
final Directory versionADir = versionsDir.childDirectory('A');
247+
final Directory resourcesDir = versionADir.childDirectory('Resources');
248+
await resourcesDir.create(recursive: true);
249+
final File dylibFile = versionADir.childFile(name);
250+
final Link currentLink = versionsDir.childLink('Current');
251+
await currentLink.create(fileSystem.path.relative(
252+
versionADir.path,
253+
from: currentLink.parent.path,
254+
));
255+
final Link resourcesLink = frameworkDir.childLink('Resources');
256+
await resourcesLink.create(fileSystem.path.relative(
257+
resourcesDir.path,
258+
from: resourcesLink.parent.path,
259+
));
260+
await lipoDylibs(dylibFile, sources);
261+
final Link dylibLink = frameworkDir.childLink(name);
262+
await dylibLink.create(fileSystem.path.relative(
263+
versionsDir.childDirectory('Current').childFile(name).path,
264+
from: dylibLink.parent.path,
265+
));
266+
await setInstallNameDylib(dylibFile);
267+
await createInfoPlist(name, resourcesDir);
268+
await codesignDylib(codesignIdentity, buildMode, frameworkDir);
269+
}
270+
globals.logger.printTrace('Copying native assets done.');
271+
}
272+
}
273+
274+
275+
/// Copies native assets for flutter tester.
276+
///
277+
/// For `flutter run -release` a multi-architecture solution is needed. So,
278+
/// `lipo` is used to combine all target architectures into a single file.
279+
///
280+
/// In contrast to [_copyNativeAssetsMacOS], it does not set the install name.
281+
///
282+
/// Code signing is also done here.
283+
Future<void> _copyNativeAssetsMacOSFlutterTester(
284+
Uri buildUri,
285+
Map<AssetPath, List<Asset>> assetTargetLocations,
286+
String? codesignIdentity,
287+
BuildMode buildMode,
288+
FileSystem fileSystem,
289+
) async {
290+
if (assetTargetLocations.isNotEmpty) {
291+
globals.logger.printTrace('Copying native assets to ${buildUri.toFilePath()}.');
292+
for (final MapEntry<AssetPath, List<Asset>> assetMapping in assetTargetLocations.entries) {
293+
final Uri target = (assetMapping.key as AssetAbsolutePath).uri;
294+
final List<Uri> sources = <Uri>[
295+
for (final Asset source in assetMapping.value)
296+
(source.path as AssetAbsolutePath).uri,
297+
];
298+
final Uri targetUri = buildUri.resolveUri(target);
299+
final File dylibFile = fileSystem.file(targetUri);
300+
final Directory targetParent = dylibFile.parent;
301+
if (!await targetParent.exists()) {
302+
await targetParent.create(recursive: true);
303+
}
304+
await lipoDylibs(dylibFile, sources);
305+
await codesignDylib(codesignIdentity, buildMode, dylibFile);
306+
}
307+
globals.logger.printTrace('Copying native assets done.');
308+
}
309+
}

0 commit comments

Comments
 (0)