Share common logic between UnpackMacOS and UnpackIOS build targets (#168034)

`UnpackIOS` and `UnpackMacOS` have shared logic, which is currently
basically duplicate code. We also plan to add more shared logic for
SwiftPM. To simply that, this PR creates an abstract `UnpackDarwin`
class with the shared logic.

No functionality is changed other than slightly changed error logs for
`UnpackMacOS`. Code is tested by:

-
https://github.com/flutter/flutter/blob/master/packages/flutter_tools/test/general.shard/build_system/targets/ios_test.dart
-
https://github.com/flutter/flutter/blob/master/packages/flutter_tools/test/general.shard/build_system/targets/macos_test.dart

Fixes https://github.com/flutter/flutter/issues/168029.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
Victoria Ashworth 2025-05-02 11:09:41 -05:00 committed by GitHub
parent 057a77b49e
commit 07b0983657
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 138 additions and 158 deletions

View File

@ -4,8 +4,10 @@
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import '../../artifacts.dart';
import '../../base/common.dart'; import '../../base/common.dart';
import '../../base/file_system.dart'; import '../../base/file_system.dart';
import '../../base/io.dart';
import '../../build_info.dart'; import '../../build_info.dart';
import '../../flutter_plugins.dart'; import '../../flutter_plugins.dart';
import '../../globals.dart' as globals; import '../../globals.dart' as globals;
@ -152,3 +154,106 @@ abstract class CheckDevDependencies extends Target {
globals.stdio.stderrWrite('error: $message'); globals.stdio.stderrWrite('error: $message');
} }
} }
abstract class UnpackDarwin extends Target {
const UnpackDarwin();
/// Copies the [framework] artifact using `rsync` to the [environment.outputDir].
/// Throws an error if copy fails.
@protected
Future<void> copyFramework(
Environment environment, {
EnvironmentType? environmentType,
TargetPlatform? targetPlatform,
required Artifact framework,
required BuildMode buildMode,
}) async {
final String basePath = environment.artifacts.getArtifactPath(
framework,
platform: targetPlatform,
mode: buildMode,
environmentType: environmentType,
);
final ProcessResult result = await environment.processManager.run(<String>[
'rsync',
'-av',
'--delete',
'--filter',
'- .DS_Store/',
'--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r',
basePath,
environment.outputDir.path,
]);
if (result.exitCode != 0) {
throw Exception(
'Failed to copy framework (exit ${result.exitCode}:\n'
'${result.stdout}\n---\n${result.stderr}',
);
}
}
/// Verifies and destructively thins the framework binary found at [frameworkBinaryPath]
/// to include only the architectures specified in [archs].
///
/// [archs] should be a space separated list passed from Xcode containing one or
/// more architectures (e.g. "x86_64 arm64", "arm64", "x86_64").
///
/// Throws an error if the binary does not contain the [archs] or fails to thin.
@protected
Future<void> thinFramework(
Environment environment,
String frameworkBinaryPath,
String archs,
) async {
final List<String> archList = archs.split(' ').toList();
final ProcessResult infoResult = await environment.processManager.run(<String>[
'lipo',
'-info',
frameworkBinaryPath,
]);
final String lipoInfo = infoResult.stdout as String;
final ProcessResult verifyResult = await environment.processManager.run(<String>[
'lipo',
frameworkBinaryPath,
'-verify_arch',
...archList,
]);
if (verifyResult.exitCode != 0) {
throw Exception(
'Binary $frameworkBinaryPath does not contain architectures "$archs".\n'
'\n'
'lipo -info:\n'
'$lipoInfo',
);
}
// Skip thinning for non-fat executables.
if (lipoInfo.startsWith('Non-fat file:')) {
environment.logger.printTrace('Skipping lipo for non-fat file $frameworkBinaryPath');
return;
}
// Thin in-place.
final ProcessResult extractResult = await environment.processManager.run(<String>[
'lipo',
'-output',
frameworkBinaryPath,
for (final String arch in archList) ...<String>['-extract', arch],
frameworkBinaryPath,
]);
if (extractResult.exitCode != 0) {
throw Exception(
'Failed to extract architectures "$archs" for $frameworkBinaryPath.\n'
'\n'
'stderr:\n'
'${extractResult.stderr}\n\n'
'lipo -info:\n'
'$lipoInfo',
);
}
}
}

View File

@ -239,7 +239,7 @@ class DebugUniversalFramework extends Target {
/// This class is abstract to share logic between the three concrete /// This class is abstract to share logic between the three concrete
/// implementations. The shelling out is done to avoid complications with /// implementations. The shelling out is done to avoid complications with
/// preserving special files (e.g., symbolic links) in the framework structure. /// preserving special files (e.g., symbolic links) in the framework structure.
abstract class UnpackIOS extends Target { abstract class UnpackIOS extends UnpackDarwin {
const UnpackIOS(); const UnpackIOS();
@override @override
@ -271,7 +271,20 @@ abstract class UnpackIOS extends Target {
if (archs == null) { if (archs == null) {
throw MissingDefineException(kIosArchs, name); throw MissingDefineException(kIosArchs, name);
} }
await _copyFramework(environment, sdkRoot);
// Copy Flutter framework.
final EnvironmentType? environmentType = environmentTypeFromSdkroot(
sdkRoot,
environment.fileSystem,
);
await copyFramework(
environment,
environmentType: environmentType,
framework: Artifact.flutterFramework,
targetPlatform: TargetPlatform.ios,
buildMode: buildMode,
);
await _copyFrameworkDysm(environment, sdkRoot: sdkRoot, environmentType: environmentType);
final File frameworkBinary = environment.outputDir final File frameworkBinary = environment.outputDir
.childDirectory('Flutter.framework') .childDirectory('Flutter.framework')
@ -280,40 +293,15 @@ abstract class UnpackIOS extends Target {
if (!await frameworkBinary.exists()) { if (!await frameworkBinary.exists()) {
throw Exception('Binary $frameworkBinaryPath does not exist, cannot thin'); throw Exception('Binary $frameworkBinaryPath does not exist, cannot thin');
} }
await _thinFramework(environment, frameworkBinaryPath, archs); await thinFramework(environment, frameworkBinaryPath, archs);
await _signFramework(environment, frameworkBinary, buildMode); await _signFramework(environment, frameworkBinary, buildMode);
} }
Future<void> _copyFramework(Environment environment, String sdkRoot) async { Future<void> _copyFrameworkDysm(
// Copy Flutter framework. Environment environment, {
final EnvironmentType? environmentType = environmentTypeFromSdkroot( required String sdkRoot,
sdkRoot, EnvironmentType? environmentType,
environment.fileSystem, }) async {
);
final String basePath = environment.artifacts.getArtifactPath(
Artifact.flutterFramework,
platform: TargetPlatform.ios,
mode: buildMode,
environmentType: environmentType,
);
final ProcessResult result = await environment.processManager.run(<String>[
'rsync',
'-av',
'--delete',
'--filter',
'- .DS_Store/',
'--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r',
basePath,
environment.outputDir.path,
]);
if (result.exitCode != 0) {
throw Exception(
'Failed to copy framework (exit ${result.exitCode}:\n'
'${result.stdout}\n---\n${result.stderr}',
);
}
// Copy Flutter framework dSYM (debug symbol) bundle, if present. // Copy Flutter framework dSYM (debug symbol) bundle, if present.
final Directory frameworkDsym = environment.fileSystem.directory( final Directory frameworkDsym = environment.fileSystem.directory(
environment.artifacts.getArtifactPath( environment.artifacts.getArtifactPath(
@ -342,63 +330,6 @@ abstract class UnpackIOS extends Target {
} }
} }
} }
/// Destructively thin Flutter.framework to include only the specified architectures.
Future<void> _thinFramework(
Environment environment,
String frameworkBinaryPath,
String archs,
) async {
final List<String> archList = archs.split(' ').toList();
final ProcessResult infoResult = await environment.processManager.run(<String>[
'lipo',
'-info',
frameworkBinaryPath,
]);
final String lipoInfo = infoResult.stdout as String;
final ProcessResult verifyResult = await environment.processManager.run(<String>[
'lipo',
frameworkBinaryPath,
'-verify_arch',
...archList,
]);
if (verifyResult.exitCode != 0) {
throw Exception(
'Binary $frameworkBinaryPath does not contain architectures "$archs".\n'
'\n'
'lipo -info:\n'
'$lipoInfo',
);
}
// Skip thinning for non-fat executables.
if (lipoInfo.startsWith('Non-fat file:')) {
environment.logger.printTrace('Skipping lipo for non-fat file $frameworkBinaryPath');
return;
}
// Thin in-place.
final ProcessResult extractResult = await environment.processManager.run(<String>[
'lipo',
'-output',
frameworkBinaryPath,
for (final String arch in archList) ...<String>['-extract', arch],
...<String>[frameworkBinaryPath],
]);
if (extractResult.exitCode != 0) {
throw Exception(
'Failed to extract architectures "$archs" for $frameworkBinaryPath.\n'
'\n'
'stderr:\n'
'${extractResult.stderr}\n\n'
'lipo -info:\n'
'$lipoInfo',
);
}
}
} }
/// Unpack the release prebuilt engine framework. /// Unpack the release prebuilt engine framework.

View File

@ -32,7 +32,7 @@ import 'native_assets.dart';
/// * [DebugUnpackMacOS] /// * [DebugUnpackMacOS]
/// * [ProfileUnpackMacOS] /// * [ProfileUnpackMacOS]
/// * [ReleaseUnpackMacOS] /// * [ReleaseUnpackMacOS]
abstract class UnpackMacOS extends Target { abstract class UnpackMacOS extends UnpackDarwin {
const UnpackMacOS(); const UnpackMacOS();
@override @override
@ -55,30 +55,15 @@ abstract class UnpackMacOS extends Target {
throw MissingDefineException(kBuildMode, 'unpack_macos'); throw MissingDefineException(kBuildMode, 'unpack_macos');
} }
// Copy Flutter framework. // Copy FlutterMacOS framework.
final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment); final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment);
final String basePath = environment.artifacts.getArtifactPath( await copyFramework(
Artifact.flutterMacOSFramework, environment,
mode: buildMode, framework: Artifact.flutterMacOSFramework,
buildMode: buildMode,
); );
final ProcessResult result = environment.processManager.runSync(<String>[
'rsync',
'-av',
'--delete',
'--filter',
'- .DS_Store/',
'--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r',
basePath,
environment.outputDir.path,
]);
_removeDenylistedFiles(environment.outputDir); _removeDenylistedFiles(environment.outputDir);
if (result.exitCode != 0) {
throw Exception(
'Failed to copy framework (exit ${result.exitCode}:\n'
'${result.stdout}\n---\n${result.stderr}',
);
}
final File frameworkBinary = environment.outputDir final File frameworkBinary = environment.outputDir
.childDirectory('FlutterMacOS.framework') .childDirectory('FlutterMacOS.framework')
@ -90,7 +75,11 @@ abstract class UnpackMacOS extends Target {
throw Exception('Binary $frameworkBinaryPath does not exist, cannot thin'); throw Exception('Binary $frameworkBinaryPath does not exist, cannot thin');
} }
await _thinFramework(environment, frameworkBinaryPath); await thinFramework(
environment,
frameworkBinaryPath,
environment.defines[kDarwinArchs] ?? 'x86_64 arm64',
);
} }
/// Files that should not be copied to build output directory if found during framework copy step. /// Files that should not be copied to build output directory if found during framework copy step.
@ -110,51 +99,6 @@ abstract class UnpackMacOS extends Target {
} }
} }
} }
Future<void> _thinFramework(Environment environment, String frameworkBinaryPath) async {
final String archs = environment.defines[kDarwinArchs] ?? 'x86_64 arm64';
final List<String> archList = archs.split(' ').toList();
final ProcessResult infoResult = await environment.processManager.run(<String>[
'lipo',
'-info',
frameworkBinaryPath,
]);
final String lipoInfo = infoResult.stdout as String;
final ProcessResult verifyResult = await environment.processManager.run(<String>[
'lipo',
frameworkBinaryPath,
'-verify_arch',
...archList,
]);
if (verifyResult.exitCode != 0) {
throw Exception(
'Binary $frameworkBinaryPath does not contain $archs. Running lipo -info:\n$lipoInfo',
);
}
// Skip thinning for non-fat executables.
if (lipoInfo.startsWith('Non-fat file:')) {
environment.logger.printTrace('Skipping lipo for non-fat file $frameworkBinaryPath');
return;
}
// Thin in-place.
final ProcessResult extractResult = environment.processManager.runSync(<String>[
'lipo',
'-output',
frameworkBinaryPath,
for (final String arch in archList) ...<String>['-extract', arch],
...<String>[frameworkBinaryPath],
]);
if (extractResult.exitCode != 0) {
throw Exception(
'Failed to extract $archs for $frameworkBinaryPath.\n${extractResult.stderr}\nRunning lipo -info:\n$lipoInfo',
);
}
}
} }
/// Unpack the release prebuilt engine framework. /// Unpack the release prebuilt engine framework.

View File

@ -286,7 +286,7 @@ void main() {
(Exception exception) => exception.toString(), (Exception exception) => exception.toString(),
'description', 'description',
contains( contains(
'does not contain arm64 x86_64. Running lipo -info:\nArchitectures in the fat file:', 'does not contain architectures "arm64 x86_64".\n\nlipo -info:\nArchitectures in the fat file:',
), ),
), ),
), ),