Skip to content

Commit f0860d8

Browse files
authored
[native assets] Rewrite install names for relocated native libraries (#153054)
Native libraries that are contributed by native asset builders can depend on each other. For macOS and iOS, native libraries are repackaged into Frameworks, which renders install names that have been written into dependent libraries invalid. With this change, a mapping between old and new install names is maintained, and install names in dependent libraries are rewritten as a final step. Related to dart-lang/native#190
1 parent 033e9b7 commit f0860d8

21 files changed

Lines changed: 834 additions & 115 deletions

packages/flutter_tools/lib/executable.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import 'src/isolated/mustache_template.dart';
5252
import 'src/isolated/native_assets/native_assets.dart';
5353
import 'src/isolated/native_assets/test/native_assets.dart';
5454
import 'src/isolated/resident_web_runner.dart';
55+
import 'src/native_assets.dart';
5556
import 'src/pre_run_validator.dart';
5657
import 'src/project_validator.dart';
5758
import 'src/resident_runner.dart';
@@ -141,6 +142,7 @@ Future<void> main(List<String> args) async {
141142
// runner.run calls "terminal.applyFeatureFlags()"
142143
},
143144
PreRunValidator: () => PreRunValidator(fileSystem: globals.fs),
145+
TestCompilerNativeAssetsBuilder: () => const TestCompilerNativeAssetsBuilderImpl(),
144146
},
145147
shutdownHooks: globals.shutdownHooks,
146148
);
@@ -249,7 +251,7 @@ List<FlutterCommand> generateCommands({
249251
TestCommand(
250252
verboseHelp: verboseHelp,
251253
verbose: verbose,
252-
nativeAssetsBuilder: const TestCompilerNativeAssetsBuilderImpl(),
254+
nativeAssetsBuilder: globals.nativeAssetsBuilder,
253255
),
254256
UpgradeCommand(verboseHelp: verboseHelp),
255257
SymbolizeCommand(

packages/flutter_tools/lib/src/commands/test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
592592
shardIndex: shardIndex,
593593
totalShards: totalShards,
594594
testTimeRecorder: testTimeRecorder,
595+
nativeAssetsBuilder: nativeAssetsBuilder,
595596
);
596597
} else {
597598
result = await testRunner.runTests(

packages/flutter_tools/lib/src/context_runner.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ Future<T> runInContext<T>(
214214
fuchsiaSdk: globals.fuchsiaSdk!,
215215
operatingSystemUtils: globals.os,
216216
customDevicesConfig: globals.customDevicesConfig,
217+
nativeAssetsBuilder: globals.nativeAssetsBuilder,
217218
),
218219
DevtoolsLauncher: () => DevtoolsServerLauncher(
219220
processManager: globals.processManager,

packages/flutter_tools/lib/src/flutter_device_manager.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ import 'macos/macos_device.dart';
2727
import 'macos/macos_ipad_device.dart';
2828
import 'macos/macos_workflow.dart';
2929
import 'macos/xcdevice.dart';
30+
import 'native_assets.dart';
3031
import 'preview_device.dart';
3132
import 'tester/flutter_tester.dart';
3233
import 'version.dart';
3334
import 'web/web_device.dart';
34-
3535
import 'windows/windows_device.dart';
3636
import 'windows/windows_workflow.dart';
3737

@@ -57,6 +57,7 @@ class FlutterDeviceManager extends DeviceManager {
5757
required OperatingSystemUtils operatingSystemUtils,
5858
required WindowsWorkflow windowsWorkflow,
5959
required CustomDevicesConfig customDevicesConfig,
60+
required TestCompilerNativeAssetsBuilder? nativeAssetsBuilder,
6061
}) : deviceDiscoverers = <DeviceDiscovery>[
6162
AndroidDevices(
6263
logger: logger,
@@ -88,6 +89,7 @@ class FlutterDeviceManager extends DeviceManager {
8889
processManager: processManager,
8990
logger: logger,
9091
artifacts: artifacts,
92+
nativeAssetsBuilder: nativeAssetsBuilder,
9193
),
9294
MacOSDevices(
9395
processManager: processManager,

packages/flutter_tools/lib/src/globals.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import 'macos/cocoapods.dart';
4141
import 'macos/cocoapods_validator.dart';
4242
import 'macos/xcdevice.dart';
4343
import 'macos/xcode.dart';
44+
import 'native_assets.dart';
4445
import 'persistent_tool_state.dart';
4546
import 'pre_run_validator.dart';
4647
import 'project.dart';
@@ -305,3 +306,5 @@ NonNullSafeBuilds get nonNullSafeBuilds => context.get<NonNullSafeBuilds>() ?? N
305306
/// A value of [null] indicates that no installation of java could be found on
306307
/// the host machine.
307308
Java? get java => context.get<Java>();
309+
310+
TestCompilerNativeAssetsBuilder? get nativeAssetsBuilder => context.get<TestCompilerNativeAssetsBuilder>();

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

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import 'package:native_assets_cli/native_assets_cli_internal.dart';
99
import '../../../base/file_system.dart';
1010
import '../../../build_info.dart';
1111
import '../../../globals.dart' as globals;
12-
1312
import '../macos/native_assets_host.dart';
1413
import '../native_assets.dart';
1514

@@ -232,8 +231,10 @@ final KernelAssetPath kernelAssetPath;
232231
/// For `flutter run -release` a multi-architecture solution is needed. So,
233232
/// `lipo` is used to combine all target architectures into a single file.
234233
///
235-
/// The install name is set so that it matches what the place it will
236-
/// be bundled in the final app.
234+
/// The install name is set so that it matches with the place it will
235+
/// be bundled in the final app. Install names that are referenced in dependent
236+
/// libraries are updated to match the new install name, so that the referenced
237+
/// library can be found by the dynamic linker.
237238
///
238239
/// Code signing is also done here, so that it doesn't have to be done in
239240
/// in xcode_backend.dart.
@@ -247,11 +248,15 @@ Future<void> _copyNativeAssetsIOS(
247248
if (assetTargetLocations.isNotEmpty) {
248249
globals.logger
249250
.printTrace('Copying native assets to ${buildUri.toFilePath()}.');
251+
252+
final Map<String, String> oldToNewInstallNames = <String, String>{};
253+
final List<(File, String, Directory)> dylibs = <(File, String, Directory)>[];
254+
250255
for (final MapEntry<KernelAssetPath, List<AssetImpl>> assetMapping
251256
in assetTargetLocations.entries) {
252257
final Uri target = (assetMapping.key as KernelAssetAbsolutePath).uri;
253-
final List<Uri> sources = <Uri>[
254-
for (final AssetImpl source in assetMapping.value) source.file!
258+
final List<File> sources = <File>[
259+
for (final AssetImpl source in assetMapping.value) fileSystem.file(source.file)
255260
];
256261
final Uri targetUri = buildUri.resolveUri(target);
257262
final File dylibFile = fileSystem.file(targetUri);
@@ -260,12 +265,25 @@ Future<void> _copyNativeAssetsIOS(
260265
await frameworkDir.create(recursive: true);
261266
}
262267
await lipoDylibs(dylibFile, sources);
263-
await setInstallNameDylib(dylibFile);
268+
269+
final String dylibFileName = dylibFile.basename;
270+
final String newInstallName = '@rpath/$dylibFileName.framework/$dylibFileName';
271+
final Set<String> oldInstallNames = await getInstallNamesDylib(dylibFile);
272+
for (final String oldInstallName in oldInstallNames) {
273+
oldToNewInstallNames[oldInstallName] = newInstallName;
274+
}
275+
dylibs.add((dylibFile, newInstallName, frameworkDir));
276+
264277
// TODO(knopp): Wire the value once there is a way to configure that in the hook.
265278
// https://github.com/dart-lang/native/issues/1133
266279
await createInfoPlist(targetUri.pathSegments.last, frameworkDir, minimumIOSVersion: '12.0');
280+
}
281+
282+
for (final (File dylibFile, String newInstallName, Directory frameworkDir) in dylibs) {
283+
await setInstallNamesDylib(dylibFile, newInstallName, oldToNewInstallNames);
267284
await codesignDylib(codesignIdentity, buildMode, frameworkDir);
268285
}
286+
269287
globals.logger.printTrace('Copying native assets done.');
270288
}
271289
}

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

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,10 @@ KernelAsset _targetLocationMacOS(
274274
/// For `flutter run -release` a multi-architecture solution is needed. So,
275275
/// `lipo` is used to combine all target architectures into a single file.
276276
///
277-
/// The install name is set so that it matches what the place it will
278-
/// be bundled in the final app.
277+
/// The install name is set so that it matches with the place it will
278+
/// be bundled in the final app. Install names that are referenced in dependent
279+
/// libraries are updated to match the new install name, so that the referenced
280+
/// library can be found the dynamic linker.
279281
///
280282
/// Code signing is also done here, so that it doesn't have to be done in
281283
/// in macos_assemble.sh.
@@ -290,11 +292,15 @@ Future<void> _copyNativeAssetsMacOS(
290292
globals.logger.printTrace(
291293
'Copying native assets to ${buildUri.toFilePath()}.',
292294
);
295+
296+
final Map<String, String> oldToNewInstallNames = <String, String>{};
297+
final List<(File, String, Directory)> dylibs = <(File, String, Directory)>[];
298+
293299
for (final MapEntry<KernelAssetPath, List<AssetImpl>> assetMapping
294300
in assetTargetLocations.entries) {
295301
final Uri target = (assetMapping.key as KernelAssetAbsolutePath).uri;
296-
final List<Uri> sources = <Uri>[
297-
for (final AssetImpl source in assetMapping.value) source.file!,
302+
final List<File> sources = <File>[
303+
for (final AssetImpl source in assetMapping.value) fileSystem.file(source.file),
298304
];
299305
final Uri targetUri = buildUri.resolveUri(target);
300306
final String name = targetUri.pathSegments.last;
@@ -332,15 +338,28 @@ Future<void> _copyNativeAssetsMacOS(
332338
versionsDir.childDirectory('Current').childFile(name).path,
333339
from: dylibLink.parent.path,
334340
));
335-
await setInstallNameDylib(dylibFile);
341+
342+
final String dylibFileName = dylibFile.basename;
343+
final String newInstallName = '@rpath/$dylibFileName.framework/$dylibFileName';
344+
final Set<String> oldInstallNames = await getInstallNamesDylib(dylibFile);
345+
for (final String oldInstallName in oldInstallNames) {
346+
oldToNewInstallNames[oldInstallName] = newInstallName;
347+
}
348+
dylibs.add((dylibFile, newInstallName, frameworkDir));
349+
336350
await createInfoPlist(name, resourcesDir);
351+
}
352+
353+
for (final (File dylibFile, String newInstallName, Directory frameworkDir) in dylibs) {
354+
await setInstallNamesDylib(dylibFile, newInstallName, oldToNewInstallNames);
337355
// Do not code-sign the libraries here with identity. Code-signing
338356
// for bundled dylibs is done in `macos_assemble.sh embed` because the
339357
// "Flutter Assemble" target does not have access to the signing identity.
340358
if (codesignIdentity != null) {
341359
await codesignDylib(codesignIdentity, buildMode, frameworkDir);
342360
}
343361
}
362+
344363
globals.logger.printTrace('Copying native assets done.');
345364
}
346365
}
@@ -350,7 +369,10 @@ Future<void> _copyNativeAssetsMacOS(
350369
/// For `flutter run -release` a multi-architecture solution is needed. So,
351370
/// `lipo` is used to combine all target architectures into a single file.
352371
///
353-
/// In contrast to [_copyNativeAssetsMacOS], it does not set the install name.
372+
/// The install names are set to the absolute paths from which the
373+
/// flutter_tester executable with load them. Install names that are
374+
/// referenced in dependent libraries are updated to match the new install name,
375+
/// so that the referenced library can be found the dynamic linker.
354376
///
355377
/// Code signing is also done here.
356378
Future<void> _copyNativeAssetsMacOSFlutterTester(
@@ -364,11 +386,15 @@ Future<void> _copyNativeAssetsMacOSFlutterTester(
364386
globals.logger.printTrace(
365387
'Copying native assets to ${buildUri.toFilePath()}.',
366388
);
389+
390+
final Map<String, String> oldToNewInstallNames = <String, String>{};
391+
final List<(File, String)> dylibs = <(File, String)>[];
392+
367393
for (final MapEntry<KernelAssetPath, List<AssetImpl>> assetMapping
368394
in assetTargetLocations.entries) {
369395
final Uri target = (assetMapping.key as KernelAssetAbsolutePath).uri;
370-
final List<Uri> sources = <Uri>[
371-
for (final AssetImpl source in assetMapping.value) source.file!,
396+
final List<File> sources = <File>[
397+
for (final AssetImpl source in assetMapping.value) fileSystem.file(source.file),
372398
];
373399
final Uri targetUri = buildUri.resolveUri(target);
374400
final File dylibFile = fileSystem.file(targetUri);
@@ -377,8 +403,19 @@ Future<void> _copyNativeAssetsMacOSFlutterTester(
377403
await targetParent.create(recursive: true);
378404
}
379405
await lipoDylibs(dylibFile, sources);
406+
final String newInstallName = dylibFile.path;
407+
final Set<String> oldInstallNames = await getInstallNamesDylib(dylibFile);
408+
for (final String oldInstallName in oldInstallNames) {
409+
oldToNewInstallNames[oldInstallName] = newInstallName;
410+
}
411+
dylibs.add((dylibFile, newInstallName));
412+
}
413+
414+
for (final (File dylibFile, String newInstallName) in dylibs) {
415+
await setInstallNamesDylib(dylibFile, newInstallName, oldToNewInstallNames);
380416
await codesignDylib(codesignIdentity, buildMode, dylibFile);
381417
}
418+
382419
globals.logger.printTrace('Copying native assets done.');
383420
}
384421
}

0 commit comments

Comments
 (0)