mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
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.
This commit is contained in:
parent
77c3807c80
commit
2e229be2ff
@ -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')),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -0,0 +1,68 @@
|
||||
// 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',
|
||||
dylibPath,
|
||||
'libbar.dylib',
|
||||
],
|
||||
),
|
||||
const FakeCommand(
|
||||
command: <Pattern>[
|
||||
'install_name_tool',
|
||||
'-id',
|
||||
'@executable_path/Frameworks/bar.dylib',
|
||||
'/build/native_assets/macos/bar.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);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user