Unverified Commit 2e229be2 authored by Daco Harkes's avatar Daco Harkes Committed by GitHub

Native assets: package in framework on iOS and MacOS (#140907)

Packages the native assets for iOS and MacOS in frameworks.

Issue:

* https://github.com/flutter/flutter/issues/140544
* https://github.com/flutter/flutter/issues/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.
parent 77c3807c
......@@ -250,9 +250,7 @@ dependencies:
checkDirectoryNotExists(path.join(ephemeralIOSHostApp.path, 'Frameworks', '$dartPluginName.framework'));
// Native assets embedded, no embedded framework.
const String libFfiPackageDylib = 'lib$ffiPackageName.dylib';
checkFileExists(path.join(ephemeralIOSHostApp.path, 'Frameworks', libFfiPackageDylib));
checkDirectoryNotExists(path.join(ephemeralIOSHostApp.path, 'Frameworks', '$ffiPackageName.framework'));
checkFileExists(path.join(ephemeralIOSHostApp.path, 'Frameworks', '$ffiPackageName.framework', ffiPackageName));
section('Clean and pub get module');
......@@ -385,7 +383,8 @@ end
checkFileExists(path.join(
hostFrameworksDirectory,
libFfiPackageDylib,
'$ffiPackageName.framework',
ffiPackageName,
));
section('Check the NOTICE file is correct');
......@@ -491,7 +490,8 @@ end
checkFileExists(path.join(
archivedAppPath,
'Frameworks',
libFfiPackageDylib,
'$ffiPackageName.framework',
ffiPackageName,
));
// The host app example builds plugins statically, url_launcher_ios.framework
......
......@@ -106,7 +106,7 @@ Future<List<Uri>> buildNativeAssetsIOS({
ensureNoLinkModeStatic(nativeAssets);
globals.logger.printTrace('Building native assets for $targets done.');
final Map<AssetPath, List<Asset>> fatAssetTargetLocations = _fatAssetTargetLocations(nativeAssets);
await copyNativeAssetsMacOSHost(
await _copyNativeAssetsIOS(
buildUri,
fatAssetTargetLocations,
codesignIdentity,
......@@ -145,21 +145,25 @@ Target _getNativeTarget(DarwinArch darwinArch) {
}
Map<AssetPath, List<Asset>> _fatAssetTargetLocations(List<Asset> nativeAssets) {
final Set<String> alreadyTakenNames = <String>{};
final Map<AssetPath, List<Asset>> result = <AssetPath, List<Asset>>{};
for (final Asset asset in nativeAssets) {
final AssetPath path = _targetLocationIOS(asset).path;
final AssetPath path = _targetLocationIOS(asset, alreadyTakenNames).path;
result[path] ??= <Asset>[];
result[path]!.add(asset);
}
return result;
}
Map<Asset, Asset> _assetTargetLocations(List<Asset> nativeAssets) => <Asset, Asset>{
for (final Asset asset in nativeAssets)
asset: _targetLocationIOS(asset),
};
Map<Asset, Asset> _assetTargetLocations(List<Asset> nativeAssets) {
final Set<String> alreadyTakenNames = <String>{};
return <Asset, Asset>{
for (final Asset asset in nativeAssets)
asset: _targetLocationIOS(asset, alreadyTakenNames),
};
}
Asset _targetLocationIOS(Asset asset) {
Asset _targetLocationIOS(Asset asset, Set<String> alreadyTakenNames) {
final AssetPath path = asset.path;
switch (path) {
case AssetSystemPath _:
......@@ -168,7 +172,52 @@ Asset _targetLocationIOS(Asset asset) {
return asset;
case AssetAbsolutePath _:
final String fileName = path.uri.pathSegments.last;
return asset.copyWith(path: AssetAbsolutePath(Uri(path: fileName)));
return asset.copyWith(
path: AssetAbsolutePath(frameworkUri(fileName, alreadyTakenNames)),
);
}
throw Exception(
'Unsupported asset path type ${path.runtimeType} in asset $asset');
}
/// Copies native assets into a framework per dynamic library.
///
/// For `flutter run -release` a multi-architecture solution is needed. So,
/// `lipo` is used to combine all target architectures into a single file.
///
/// The install name is set so that it matches what the place it will
/// be bundled in the final app.
///
/// Code signing is also done here, so that it doesn't have to be done in
/// in xcode_backend.dart.
Future<void> _copyNativeAssetsIOS(
Uri buildUri,
Map<AssetPath, List<Asset>> assetTargetLocations,
String? codesignIdentity,
BuildMode buildMode,
FileSystem fileSystem,
) async {
if (assetTargetLocations.isNotEmpty) {
globals.logger
.printTrace('Copying native assets to ${buildUri.toFilePath()}.');
for (final MapEntry<AssetPath, List<Asset>> assetMapping
in assetTargetLocations.entries) {
final Uri target = (assetMapping.key as AssetAbsolutePath).uri;
final List<Uri> sources = <Uri>[
for (final Asset source in assetMapping.value)
(source.path as AssetAbsolutePath).uri
];
final Uri targetUri = buildUri.resolveUri(target);
final File dylibFile = fileSystem.file(targetUri);
final Directory frameworkDir = dylibFile.parent;
if (!await frameworkDir.exists()) {
await frameworkDir.create(recursive: true);
}
await lipoDylibs(dylibFile, sources);
await setInstallNameDylib(dylibFile);
await createInfoPlist(targetUri.pathSegments.last, frameworkDir);
await codesignDylib(codesignIdentity, buildMode, frameworkDir);
}
globals.logger.printTrace('Copying native assets done.');
}
throw Exception('Unsupported asset path type ${path.runtimeType} in asset $asset');
}
......@@ -108,7 +108,23 @@ Future<(Uri? nativeAssetsYaml, List<Uri> dependencies)> buildNativeAssetsMacOS({
final Uri? absolutePath = flutterTester ? buildUri : null;
final Map<Asset, Asset> assetTargetLocations = _assetTargetLocations(nativeAssets, absolutePath);
final Map<AssetPath, List<Asset>> fatAssetTargetLocations = _fatAssetTargetLocations(nativeAssets, absolutePath);
await copyNativeAssetsMacOSHost(buildUri, fatAssetTargetLocations, codesignIdentity, buildMode, fileSystem);
if (flutterTester) {
await _copyNativeAssetsMacOSFlutterTester(
buildUri,
fatAssetTargetLocations,
codesignIdentity,
buildMode,
fileSystem,
);
} else {
await _copyNativeAssetsMacOS(
buildUri,
fatAssetTargetLocations,
codesignIdentity,
buildMode,
fileSystem,
);
}
final Uri nativeAssetsUri = await writeNativeAssetsYaml(assetTargetLocations.values, yamlParentDirectory ?? buildUri, fileSystem);
return (nativeAssetsUri, dependencies.toList());
}
......@@ -125,22 +141,40 @@ Target _getNativeTarget(DarwinArch darwinArch) {
}
}
Map<AssetPath, List<Asset>> _fatAssetTargetLocations(List<Asset> nativeAssets, Uri? absolutePath) {
Map<AssetPath, List<Asset>> _fatAssetTargetLocations(
List<Asset> nativeAssets,
Uri? absolutePath,
) {
final Set<String> alreadyTakenNames = <String>{};
final Map<AssetPath, List<Asset>> result = <AssetPath, List<Asset>>{};
for (final Asset asset in nativeAssets) {
final AssetPath path = _targetLocationMacOS(asset, absolutePath).path;
final AssetPath path = _targetLocationMacOS(
asset,
absolutePath,
alreadyTakenNames,
).path;
result[path] ??= <Asset>[];
result[path]!.add(asset);
}
return result;
}
Map<Asset, Asset> _assetTargetLocations(List<Asset> nativeAssets, Uri? absolutePath) => <Asset, Asset>{
for (final Asset asset in nativeAssets)
asset: _targetLocationMacOS(asset, absolutePath),
};
Map<Asset, Asset> _assetTargetLocations(
List<Asset> nativeAssets,
Uri? absolutePath,
) {
final Set<String> alreadyTakenNames = <String>{};
return <Asset, Asset>{
for (final Asset asset in nativeAssets)
asset: _targetLocationMacOS(asset, absolutePath, alreadyTakenNames),
};
}
Asset _targetLocationMacOS(Asset asset, Uri? absolutePath) {
Asset _targetLocationMacOS(
Asset asset,
Uri? absolutePath,
Set<String> alreadyTakenNames,
) {
final AssetPath path = asset.path;
switch (path) {
case AssetSystemPath _:
......@@ -157,9 +191,119 @@ Asset _targetLocationMacOS(Asset asset, Uri? absolutePath) {
// Flutter Desktop needs "absolute" paths inside the app.
// "relative" in the context of native assets would be relative to the
// kernel or aot snapshot.
uri = Uri(path: fileName);
uri = frameworkUri(fileName, alreadyTakenNames);
}
return asset.copyWith(path: AssetAbsolutePath(uri));
}
throw Exception('Unsupported asset path type ${path.runtimeType} in asset $asset');
}
/// Copies native assets into a framework per dynamic library.
///
/// The framework contains symlinks according to
/// https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/FrameworkAnatomy.html
///
/// For `flutter run -release` a multi-architecture solution is needed. So,
/// `lipo` is used to combine all target architectures into a single file.
///
/// The install name is set so that it matches what the place it will
/// be bundled in the final app.
///
/// Code signing is also done here, so that it doesn't have to be done in
/// in macos_assemble.sh.
Future<void> _copyNativeAssetsMacOS(
Uri buildUri,
Map<AssetPath, List<Asset>> assetTargetLocations,
String? codesignIdentity,
BuildMode buildMode,
FileSystem fileSystem,
) async {
if (assetTargetLocations.isNotEmpty) {
globals.logger.printTrace('Copying native assets to ${buildUri.toFilePath()}.');
for (final MapEntry<AssetPath, List<Asset>> assetMapping in assetTargetLocations.entries) {
final Uri target = (assetMapping.key as AssetAbsolutePath).uri;
final List<Uri> sources = <Uri>[
for (final Asset source in assetMapping.value)
(source.path as AssetAbsolutePath).uri,
];
final Uri targetUri = buildUri.resolveUri(target);
final String name = targetUri.pathSegments.last;
final Directory frameworkDir = fileSystem.file(targetUri).parent;
if (await frameworkDir.exists()) {
await frameworkDir.delete(recursive: true);
}
// MyFramework.framework/ frameworkDir
// MyFramework -> Versions/Current/MyFramework dylibLink
// Resources -> Versions/Current/Resources resourcesLink
// Versions/ versionsDir
// A/ versionADir
// MyFramework dylibFile
// Resources/ resourcesDir
// Info.plist
// Current -> A currentLink
final Directory versionsDir = frameworkDir.childDirectory('Versions');
final Directory versionADir = versionsDir.childDirectory('A');
final Directory resourcesDir = versionADir.childDirectory('Resources');
await resourcesDir.create(recursive: true);
final File dylibFile = versionADir.childFile(name);
final Link currentLink = versionsDir.childLink('Current');
await currentLink.create(fileSystem.path.relative(
versionADir.path,
from: currentLink.parent.path,
));
final Link resourcesLink = frameworkDir.childLink('Resources');
await resourcesLink.create(fileSystem.path.relative(
resourcesDir.path,
from: resourcesLink.parent.path,
));
await lipoDylibs(dylibFile, sources);
final Link dylibLink = frameworkDir.childLink(name);
await dylibLink.create(fileSystem.path.relative(
versionsDir.childDirectory('Current').childFile(name).path,
from: dylibLink.parent.path,
));
await setInstallNameDylib(dylibFile);
await createInfoPlist(name, resourcesDir);
await codesignDylib(codesignIdentity, buildMode, frameworkDir);
}
globals.logger.printTrace('Copying native assets done.');
}
}
/// Copies native assets for flutter tester.
///
/// For `flutter run -release` a multi-architecture solution is needed. So,
/// `lipo` is used to combine all target architectures into a single file.
///
/// In contrast to [_copyNativeAssetsMacOS], it does not set the install name.
///
/// Code signing is also done here.
Future<void> _copyNativeAssetsMacOSFlutterTester(
Uri buildUri,
Map<AssetPath, List<Asset>> assetTargetLocations,
String? codesignIdentity,
BuildMode buildMode,
FileSystem fileSystem,
) async {
if (assetTargetLocations.isNotEmpty) {
globals.logger.printTrace('Copying native assets to ${buildUri.toFilePath()}.');
for (final MapEntry<AssetPath, List<Asset>> assetMapping in assetTargetLocations.entries) {
final Uri target = (assetMapping.key as AssetAbsolutePath).uri;
final List<Uri> sources = <Uri>[
for (final Asset source in assetMapping.value)
(source.path as AssetAbsolutePath).uri,
];
final Uri targetUri = buildUri.resolveUri(target);
final File dylibFile = fileSystem.file(targetUri);
final Directory targetParent = dylibFile.parent;
if (!await targetParent.exists()) {
await targetParent.create(recursive: true);
}
await lipoDylibs(dylibFile, sources);
await codesignDylib(codesignIdentity, buildMode, dylibFile);
}
globals.logger.printTrace('Copying native assets done.');
}
}
......@@ -13,41 +13,40 @@ import '../build_info.dart';
import '../convert.dart';
import '../globals.dart' as globals;
/// The target location for native assets on macOS.
/// Create an `Info.plist` in [target] for a framework with a single dylib.
///
/// Because we need to have a multi-architecture solution for
/// `flutter run --release`, we use `lipo` to combine all target architectures
/// into a single file.
///
/// We need to set the install name so that it matches what the place it will
/// be bundled in the final app.
///
/// Code signing is also done here, so that we don't have to worry about it
/// in xcode_backend.dart and macos_assemble.sh.
Future<void> copyNativeAssetsMacOSHost(
Uri buildUri,
Map<AssetPath, List<Asset>> assetTargetLocations,
String? codesignIdentity,
BuildMode buildMode,
FileSystem fileSystem,
/// The framework must be named [name].framework and the dylib [name].
Future<void> createInfoPlist(
String name,
Directory target,
) async {
if (assetTargetLocations.isNotEmpty) {
globals.logger.printTrace('Copying native assets to ${buildUri.toFilePath()}.');
final Directory buildDir = fileSystem.directory(buildUri.toFilePath());
if (!buildDir.existsSync()) {
buildDir.createSync(recursive: true);
}
for (final MapEntry<AssetPath, List<Asset>> assetMapping in assetTargetLocations.entries) {
final Uri target = (assetMapping.key as AssetAbsolutePath).uri;
final List<Uri> sources = <Uri>[for (final Asset source in assetMapping.value) (source.path as AssetAbsolutePath).uri];
final Uri targetUri = buildUri.resolveUri(target);
final String targetFullPath = targetUri.toFilePath();
await lipoDylibs(targetFullPath, sources);
await setInstallNameDylib(targetUri);
await codesignDylib(codesignIdentity, buildMode, targetFullPath);
}
globals.logger.printTrace('Copying native assets done.');
}
final File infoPlistFile = target.childFile('Info.plist');
await infoPlistFile.writeAsString('''
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$name</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.native_assets.$name</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$name</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
</dict>
</plist>
''');
}
/// Combines dylibs from [sources] into a fat binary at [targetFullPath].
......@@ -55,13 +54,13 @@ Future<void> copyNativeAssetsMacOSHost(
/// The dylibs must have different architectures. E.g. a dylib targeting
/// arm64 ios simulator cannot be combined with a dylib targeting arm64
/// ios device or macos arm64.
Future<void> lipoDylibs(String targetFullPath, List<Uri> sources) async {
Future<void> lipoDylibs(File target, List<Uri> sources) async {
final ProcessResult lipoResult = await globals.processManager.run(
<String>[
'lipo',
'-create',
'-output',
targetFullPath,
target.path,
for (final Uri source in sources) source.toFilePath(),
],
);
......@@ -78,25 +77,27 @@ Future<void> lipoDylibs(String targetFullPath, List<Uri> sources) async {
/// dylib itself does not correspond to the path that the file is at. Therefore,
/// native assets copied into their final location also need their install name
/// updated with the `install_name_tool`.
Future<void> setInstallNameDylib(Uri targetUri) async {
final String fileName = targetUri.pathSegments.last;
Future<void> setInstallNameDylib(File dylibFile) async {
final String fileName = dylibFile.basename;
final ProcessResult installNameResult = await globals.processManager.run(
<String>[
'install_name_tool',
'-id',
'@executable_path/Frameworks/$fileName',
targetUri.toFilePath(),
'@rpath/$fileName.framework/$fileName',
dylibFile.path,
],
);
if (installNameResult.exitCode != 0) {
throwToolExit('Failed to change the install name of $targetUri:\n${installNameResult.stderr}');
throwToolExit(
'Failed to change the install name of $dylibFile:\n${installNameResult.stderr}',
);
}
}
Future<void> codesignDylib(
String? codesignIdentity,
BuildMode buildMode,
String targetFullPath,
FileSystemEntity target,
) async {
if (codesignIdentity == null || codesignIdentity.isEmpty) {
codesignIdentity = '-';
......@@ -110,12 +111,17 @@ Future<void> codesignDylib(
// Mimic Xcode's timestamp codesigning behavior on non-release binaries.
'--timestamp=none',
],
targetFullPath,
target.path,
];
globals.logger.printTrace(codesignCommand.join(' '));
final ProcessResult codesignResult = await globals.processManager.run(codesignCommand);
final ProcessResult codesignResult = await globals.processManager.run(
codesignCommand,
);
if (codesignResult.exitCode != 0) {
throwToolExit('Failed to code sign binary:\n${codesignResult.stderr}');
throwToolExit(
'Failed to code sign binary: exit code: ${codesignResult.exitCode} '
'${codesignResult.stdout} ${codesignResult.stderr}',
);
}
globals.logger.printTrace(codesignResult.stdout as String);
globals.logger.printTrace(codesignResult.stderr as String);
......@@ -125,17 +131,77 @@ Future<void> codesignDylib(
///
/// Use the `clang`, `ar`, and `ld` that would be used if run with `xcrun`.
Future<CCompilerConfig> cCompilerConfigMacOS() async {
final ProcessResult xcrunResult = await globals.processManager.run(<String>['xcrun', 'clang', '--version']);
final ProcessResult xcrunResult = await globals.processManager.run(
<String>['xcrun', 'clang', '--version'],
);
if (xcrunResult.exitCode != 0) {
throwToolExit('Failed to find clang with xcrun:\n${xcrunResult.stderr}');
}
final String installPath = LineSplitter.split(xcrunResult.stdout as String)
.firstWhere((String s) => s.startsWith('InstalledDir: '))
.split(' ')
.last;
.firstWhere((String s) => s.startsWith('InstalledDir: '))
.split(' ')
.last;
return CCompilerConfig(
cc: Uri.file('$installPath/clang'),
ar: Uri.file('$installPath/ar'),
ld: Uri.file('$installPath/ld'),
);
}
/// Converts [fileName] into a suitable framework name.
///
/// On MacOS and iOS, dylibs need to be packaged in a framework.
///
/// In order for resolution to work, the file name inside the framework must be
/// equal to the framework name.
///
/// Dylib names on MacOS/iOS usually have a dylib extension. If so, remove it.
///
/// Dylib names on MacOS/iOS are usually prefixed with 'lib'. So, if the file is
/// a dylib, try to remove the prefix.
///
/// The bundle ID string must contain only alphanumeric characters
/// (A–Z, a–z, and 0–9), hyphens (-), and periods (.).
/// https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleidentifier
///
/// This name can contain up to 15 characters.
/// https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundlename
///
/// The [alreadyTakenNames] are used to ensure that the framework name does not
/// conflict with previously chosen names.
Uri frameworkUri(String fileName, Set<String> alreadyTakenNames) {
final List<String> splitFileName = fileName.split('.');
final bool isDylib;
if (splitFileName.length >= 2) {
isDylib = splitFileName.last == 'dylib';
if (isDylib) {
fileName = splitFileName.sublist(0, splitFileName.length - 1).join('.');
}
} else {
isDylib = false;
}
if (isDylib && fileName.startsWith('lib')) {
fileName = fileName.replaceFirst('lib', '');
}
fileName = fileName.replaceAll(RegExp(r'[^A-Za-z0-9_-]'), '');
if (fileName.length > 15) {
fileName = fileName.substring(0, 15);
}
if (alreadyTakenNames.contains(fileName)) {
if (fileName.length > 12) {
fileName = fileName.substring(0, 12);
}
final String prefixName = fileName;
for (int i = 1; i < 1000; i++) {
fileName = '$prefixName$i';
if (!alreadyTakenNames.contains(fileName)) {
break;
}
}
if (alreadyTakenNames.contains(fileName)) {
throwToolExit('Failed to rename $fileName in native assets packaging.');
}
}
alreadyTakenNames.add(fileName);
return Uri(path: '$fileName.framework/$fileName');
}
......@@ -135,7 +135,7 @@ void main() {
linkMode: native_assets_cli.LinkMode.dynamic,
target: native_assets_cli.Target.iOSArm64,
path: native_assets_cli.AssetAbsolutePath(
Uri.file('libfoo.dylib'),
Uri.file('foo.framework/foo'),
),
)
], dependencies: <Uri>[
......@@ -165,7 +165,7 @@ void main() {
nativeAssetsYaml.readAsStringSync(),
stringContainsInOrder(<String>[
'package:foo/foo.dart',
'libfoo.dylib',
'foo.framework',
]),
);
},
......
......@@ -130,13 +130,13 @@ void main() {
id: 'package:bar/bar.dart',
linkMode: LinkMode.dynamic,
target: native_assets_cli.Target.macOSArm64,
path: AssetAbsolutePath(Uri.file('bar.dylib')),
path: AssetAbsolutePath(Uri.file('libbar.dylib')),
),
Asset(
id: 'package:bar/bar.dart',
linkMode: LinkMode.dynamic,
target: native_assets_cli.Target.macOSX64,
path: AssetAbsolutePath(Uri.file('bar.dylib')),
path: AssetAbsolutePath(Uri.file('libbar.dylib')),
),
],
),
......@@ -219,16 +219,16 @@ void main() {
'lipo',
'-create',
'-output',
'/build/native_assets/ios/bar.dylib',
'bar.dylib',
'/build/native_assets/ios/bar.framework/bar',
'libbar.dylib',
],
),
const FakeCommand(
command: <Pattern>[
'install_name_tool',
'-id',
'@executable_path/Frameworks/bar.dylib',
'/build/native_assets/ios/bar.dylib',
'@rpath/bar.framework/bar',
'/build/native_assets/ios/bar.framework/bar'
],
),
const FakeCommand(
......@@ -238,7 +238,7 @@ void main() {
'--sign',
'-',
'--timestamp=none',
'/build/native_assets/ios/bar.dylib',
'/build/native_assets/ios/bar.framework',
],
),
],
......@@ -267,7 +267,7 @@ void main() {
id: 'package:bar/bar.dart',
linkMode: LinkMode.dynamic,
target: native_assets_cli.Target.iOSArm64,
path: AssetAbsolutePath(Uri.file('bar.dylib')),
path: AssetAbsolutePath(Uri.file('libbar.dylib')),
),
],
),
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_tools/src/macos/native_assets_host.dart';
import '../../src/common.dart';
void main() {
test('framework name', () {
expect(
frameworkUri('libfoo.dylib', <String>{}),
equals(Uri.file('foo.framework/foo')),
);
expect(
frameworkUri('foo', <String>{}),
equals(Uri.file('foo.framework/foo')),
);
expect(
frameworkUri('foo_foo', <String>{}),
equals(Uri.file('foo_foo.framework/foo_foo')),
);
expect(
frameworkUri('foo-foo', <String>{}),
equals(Uri.file('foo-foo.framework/foo-foo')),
);
expect(
frameworkUri(r'foo$foo', <String>{}),
equals(Uri.file('foofoo.framework/foofoo')),
);
expect(
frameworkUri('foo.foo', <String>{}),
equals(Uri.file('foofoo.framework/foofoo')),
);
expect(
frameworkUri('libatoolongfilenameforaframework.dylib', <String>{}),
equals(Uri.file('atoolongfilenam.framework/atoolongfilenam')),
);
});
test('framework name name confilicts', () {
final Set<String> alreadyTakenNames = <String>{};
expect(
frameworkUri('libfoo.dylib', alreadyTakenNames),
equals(Uri.file('foo.framework/foo')),
);
expect(
frameworkUri('libfoo.dylib', alreadyTakenNames),
equals(Uri.file('foo1.framework/foo1')),
);
expect(
frameworkUri('libfoo.dylib', alreadyTakenNames),
equals(Uri.file('foo2.framework/foo2')),
);
expect(
frameworkUri('libatoolongfilenameforaframework.dylib', alreadyTakenNames),
equals(Uri.file('atoolongfilenam.framework/atoolongfilenam')),
);
expect(
frameworkUri('libatoolongfilenameforaframework.dylib', alreadyTakenNames),
equals(Uri.file('atoolongfile1.framework/atoolongfile1')),
);
expect(
frameworkUri('libatoolongfilenameforaframework.dylib', alreadyTakenNames),
equals(Uri.file('atoolongfile2.framework/atoolongfile2')),
);
});
}
......@@ -149,13 +149,13 @@ void main() {
id: 'package:bar/bar.dart',
linkMode: LinkMode.dynamic,
target: native_assets_cli.Target.macOSArm64,
path: AssetAbsolutePath(Uri.file('bar.dylib')),
path: AssetAbsolutePath(Uri.file('libbar.dylib')),
),
Asset(
id: 'package:bar/bar.dart',
linkMode: LinkMode.dynamic,
target: native_assets_cli.Target.macOSX64,
path: AssetAbsolutePath(Uri.file('bar.dylib')),
path: AssetAbsolutePath(Uri.file('libbar.dylib')),
),
],
),
......@@ -236,35 +236,47 @@ void main() {
if (flutterTester) {
testName += ' flutter tester';
}
final String dylibPath;
final String signPath;
if (flutterTester) {
// Just the dylib.
dylibPath = '/build/native_assets/macos/libbar.dylib';
signPath = '/build/native_assets/macos/libbar.dylib';
} else {
// Packaged in framework.
dylibPath = '/build/native_assets/macos/bar.framework/Versions/A/bar';
signPath = '/build/native_assets/macos/bar.framework';
}
testUsingContext('build with assets$testName', overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true),
ProcessManager: () => FakeProcessManager.list(
<FakeCommand>[
const FakeCommand(
FakeCommand(
command: <Pattern>[
'lipo',
'-create',
'-output',
'/build/native_assets/macos/bar.dylib',
'bar.dylib',
],
),
const FakeCommand(
command: <Pattern>[
'install_name_tool',
'-id',
'@executable_path/Frameworks/bar.dylib',
'/build/native_assets/macos/bar.dylib',
dylibPath,
'libbar.dylib',
],
),
const FakeCommand(
if (!flutterTester)
FakeCommand(
command: <Pattern>[
'install_name_tool',
'-id',
'@rpath/bar.framework/bar',
dylibPath,
],
),
FakeCommand(
command: <Pattern>[
'codesign',
'--force',
'--sign',
'-',
'--timestamp=none',
'/build/native_assets/macos/bar.dylib',
signPath,
],
),
],
......@@ -292,7 +304,7 @@ void main() {
id: 'package:bar/bar.dart',
linkMode: LinkMode.dynamic,
target: native_assets_cli.Target.macOSArm64,
path: AssetAbsolutePath(Uri.file('bar.dylib')),
path: AssetAbsolutePath(Uri.file('libbar.dylib')),
),
],
),
......@@ -315,10 +327,10 @@ void main() {
'package:bar/bar.dart',
if (flutterTester)
// Tests run on host system, so the have the full path on the system.
'- ${projectUri.resolve('build/native_assets/macos/bar.dylib').toFilePath()}'
'- ${projectUri.resolve('build/native_assets/macos/libbar.dylib').toFilePath()}'
else
// Apps are a bundle with the dylibs on their dlopen path.
'- bar.dylib',
'- bar.framework/bar',
]),
);
});
......
......@@ -292,18 +292,48 @@ void main() {
void expectDylibIsBundledMacOS(Directory appDirectory, String buildMode) {
final Directory appBundle = appDirectory.childDirectory('build/$hostOs/Build/Products/${buildMode.upperCaseFirst()}/$exampleAppName.app');
expect(appBundle, exists);
final Directory dylibsFolder = appBundle.childDirectory('Contents/Frameworks');
expect(dylibsFolder, exists);
final File dylib = dylibsFolder.childFile(OS.macOS.dylibFileName(packageName));
expect(dylib, exists);
final Directory frameworksFolder =
appBundle.childDirectory('Contents/Frameworks');
expect(frameworksFolder, exists);
// MyFramework.framework/
// MyFramework -> Versions/Current/MyFramework
// Resources -> Versions/Current/Resources
// Versions/
// A/
// MyFramework
// Resources/
// Info.plist
// Current -> A
final String frameworkName = packageName.substring(0, 15);
final Directory frameworkDir =
frameworksFolder.childDirectory('$frameworkName.framework');
final Directory versionsDir = frameworkDir.childDirectory('Versions');
final Directory versionADir = versionsDir.childDirectory('A');
final Directory resourcesDir = versionADir.childDirectory('Resources');
expect(resourcesDir, exists);
final File dylibFile = versionADir.childFile(frameworkName);
expect(dylibFile, exists);
final Link currentLink = versionsDir.childLink('Current');
expect(currentLink, exists);
expect(currentLink.resolveSymbolicLinksSync(), versionADir.path);
final Link resourcesLink = frameworkDir.childLink('Resources');
expect(resourcesLink, exists);
expect(resourcesLink.resolveSymbolicLinksSync(), resourcesDir.path);
final Link dylibLink = frameworkDir.childLink(frameworkName);
expect(dylibLink, exists);
expect(dylibLink.resolveSymbolicLinksSync(), dylibFile.path);
}
void expectDylibIsBundledIos(Directory appDirectory, String buildMode) {
final Directory appBundle = appDirectory.childDirectory('build/ios/${buildMode.upperCaseFirst()}-iphoneos/Runner.app');
expect(appBundle, exists);
final Directory dylibsFolder = appBundle.childDirectory('Frameworks');
expect(dylibsFolder, exists);
final File dylib = dylibsFolder.childFile(OS.iOS.dylibFileName(packageName));
final Directory frameworksFolder = appBundle.childDirectory('Frameworks');
expect(frameworksFolder, exists);
final String frameworkName = packageName.substring(0, 15);
final File dylib = frameworksFolder
.childDirectory('$frameworkName.framework')
.childFile(frameworkName);
expect(dylib, exists);
}
......@@ -379,7 +409,10 @@ void expectDylibIsBundledAndroid(Directory appDirectory, String buildMode) {
void expectDylibIsBundledWithFrameworks(Directory appDirectory, String buildMode, String os) {
final Directory frameworksFolder = appDirectory.childDirectory('build/$os/framework/${buildMode.upperCaseFirst()}');
expect(frameworksFolder, exists);
final File dylib = frameworksFolder.childFile(OS.macOS.dylibFileName(packageName));
final String frameworkName = packageName.substring(0, 15);
final File dylib = frameworksFolder
.childDirectory('$frameworkName.framework')
.childFile(frameworkName);
expect(dylib, exists);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment