Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 15 additions & 13 deletions packages/flutter_tools/bin/xcode_backend.dart
Original file line number Diff line number Diff line change
Expand Up @@ -461,18 +461,20 @@ class Context {
flutterArgs.add('--local-engine-host=${environment['LOCAL_ENGINE_HOST']}');
}

String architectures = environment['ARCHS'] ?? '';
if (command == 'prepare') {
// The "prepare" command runs in a pre-action script, which doesn't always
// filter the "ARCHS" build setting to only the active arch. To workaround,
// if "ONLY_ACTIVE_ARCH" is true and the "NATIVE_ARCH" is arm, assume the
// active arch is also arm to improve caching. If this assumption is
// incorrect, it will later be corrected by the "build" command.
if (environment['ONLY_ACTIVE_ARCH'] == 'YES' && environment['NATIVE_ARCH'] != null) {
if (environment['NATIVE_ARCH']!.contains('arm')) {
architectures = 'arm64';
} else {
architectures = 'x86_64';
// The "prepare" command runs in a pre-action script, which doesn't always
// filter the "ARCHS" build setting. Attempt to filter the architecture
// to improve caching. If this filter is incorrect, it will later be
// corrected by the "build" command.
String archs = environment['ARCHS'] ?? '';
if (command == 'prepare' && archs.contains(' ')) {
// If "ONLY_ACTIVE_ARCH" is "YES", the product includes only code for the
// native architecture ("NATIVE_ARCH").
final String? nativeArch = environment['NATIVE_ARCH'];
if (environment['ONLY_ACTIVE_ARCH'] == 'YES' && nativeArch != null) {
if (nativeArch.contains('arm64') && archs.contains('arm64')) {
archs = 'arm64';
} else if (nativeArch.contains('x86_64') && archs.contains('x86_64')) {
archs = 'x86_64';
}
}
}
Expand All @@ -485,7 +487,7 @@ class Context {
'-dTargetFile=$targetPath',
'-dBuildMode=$buildMode',
if (environment['FLAVOR'] != null) '-dFlavor=${environment['FLAVOR']}',
'-dIosArchs=$architectures',
'-dIosArchs=$archs',
'-dSdkRoot=${environment['SDKROOT'] ?? ''}',
'-dSplitDebugInfo=${environment['SPLIT_DEBUG_INFO'] ?? ''}',
'-dTreeShakeIcons=${environment['TREE_SHAKE_ICONS'] ?? ''}',
Expand Down
13 changes: 11 additions & 2 deletions packages/flutter_tools/lib/src/artifacts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -648,8 +648,17 @@ class CachedArtifacts implements Artifacts {
switch (artifact) {
case Artifact.genSnapshot:
assert(mode != BuildMode.debug, 'Artifact $artifact only available in non-debug mode.');
final String hostPlatform = getNameForHostPlatform(getCurrentHostPlatform());
return _fileSystem.path.join(engineDir, hostPlatform, _artifactToFileName(artifact, _platform));

// TODO(cbracken): Build Android gen_snapshot as Arm64 binary to run
// natively on Apple Silicon. See:
// https://github.com/flutter/flutter/issues/152281
HostPlatform hostPlatform = getCurrentHostPlatform();
if (hostPlatform == HostPlatform.darwin_arm64) {
hostPlatform = HostPlatform.darwin_x64;
}

final String hostPlatformName = getNameForHostPlatform(hostPlatform);
return _fileSystem.path.join(engineDir, hostPlatformName, _artifactToFileName(artifact, _platform));
case Artifact.engineDartSdkPath:
case Artifact.engineDartBinary:
case Artifact.engineDartAotRuntime:
Expand Down
16 changes: 15 additions & 1 deletion packages/flutter_tools/lib/src/build_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -788,9 +788,23 @@ AndroidArch getAndroidArchForName(String platform) {
};
}

DarwinArch getCurrentDarwinArch() {
return switch (globals.os.hostPlatform) {
HostPlatform.darwin_arm64 => DarwinArch.arm64,
HostPlatform.darwin_x64 => DarwinArch.x86_64,
final HostPlatform unsupported => throw Exception(
'Unsupported Darwin host platform "$unsupported"',
),
};
}

HostPlatform getCurrentHostPlatform() {
if (globals.platform.isMacOS) {
return HostPlatform.darwin_x64;
return switch (getCurrentDarwinArch()) {
DarwinArch.arm64 => HostPlatform.darwin_arm64,
DarwinArch.x86_64 => HostPlatform.darwin_x64,
DarwinArch.armv7 => throw Exception('Unsupported macOS arch "amv7"'),
};
}
if (globals.platform.isLinux) {
// support x64 and arm64 architecture.
Expand Down
9 changes: 6 additions & 3 deletions packages/flutter_tools/lib/src/ios/mac.dart
Original file line number Diff line number Diff line change
Expand Up @@ -351,12 +351,15 @@ Future<XcodeBuildResult> buildXcodeProject({
}

if (activeArch != null) {
final String activeArchName = activeArch.name;
buildCommands.add('ONLY_ACTIVE_ARCH=YES');
// Setting ARCHS to $activeArchName will break the build if a watchOS companion app exists,
// as it cannot be build for the architecture of the Flutter app.
if (!hasWatchCompanion) {
buildCommands.add('ARCHS=$activeArchName');
// ONLY_ACTIVE_ARCH specifies whether the product includes only code for
// the native architecture.
final bool onlyActiveArch = activeArch == getCurrentDarwinArch();

buildCommands.add('ONLY_ACTIVE_ARCH=${onlyActiveArch? 'YES' : 'NO'}');
buildCommands.add('ARCHS=${activeArch.name}');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A value of YES means the product includes only code for the native architecture (NATIVE_ARCH).

A value of NO means the product includes code for the architectures specified in ARCHS (Architectures).

It means the native arch of the target phone or simulator, not the host Mac arch. If this is on a Mac x64 machine but targeting a physical arm64 device, I don't think this would work?

Copy link
Member Author

@loic-sharma loic-sharma Sep 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is on a Mac x64 machine but targeting a physical arm64 device, I don't think this would work?

Here's a devicelab test that verifies this works on a Mac x64 machine targeting a physical arm64 device:

Description Host machine Target machine CI test Result
SwiftPM enabled without fix
#154750
Mac x64 Physical arm64 device Link
SwiftPM enabled with fix
#154749
Mac x64 Physical arm64 device Link

It means the native arch of the target phone or simulator, not the host Mac arch.

As in, NATIVE_ARCH is the native arch of the target phone or simulator? If so, I don't think that's true.

NATIVE_ARCH's docs:

Identifies the architecture on which the build is being performed (same as CURRENT_ARCH).

Also see this devicelab test of a x64 host for arm64 target: https://logs.chromium.org/logs/flutter/buildbucket/cr-buildbucket/8737518504062464913/+/u/run_rrect_blur_perf_ios__timeline_summary/stdout

[2024-09-06 12:18:31.283585] [STDOUT] stdout:                 export ARCHS\=arm64
...
[2024-09-06 12:18:31.286058] [STDOUT] stdout:                 export NATIVE_ARCH\=x86_64
...
[2024-09-06 12:18:31.288132] [STDOUT] stdout:             ♦ /opt/s/w/ir/x/w/rc/tmpbd9z5dzh/flutter sdk/bin/flutter --verbose assemble --no-version-check --output=/opt/s/w/ir/x/w/rc/tmpbd9z5dzh/flutter sdk/dev/benchmarks/macrobenchmarks/build/ios/Profile-iphoneos/ -dTargetPlatform=ios -dTargetFile=test_driver/run_app.dart -dBuildMode=profile -dIosArchs=arm64 -dSdkRoot=/opt/flutter/xcode/15a240d/XCode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk -dSplitDebugInfo= -dTreeShakeIcons=false -dTrackWidgetCreation=true -dDartObfuscation=false -dAction=build -dFrontendServerStarterPath= --ExtraGenSnapshotOptions= --DartDefines= --ExtraFrontEndOptions= -dCodesignIdentity=6475AE66068783D9C7566E71522EA3915C7D6C9A profile_ios_bundle_flutter_assets

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for investigating! I didn't have the details paged in.

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/version.dart';
import 'package:flutter_tools/src/build_info.dart';
Expand Down Expand Up @@ -79,6 +80,10 @@ final FakePlatform macPlatform = FakePlatform(
environment: <String, String>{},
);

final FakeOperatingSystemUtils os = FakeOperatingSystemUtils(
hostPlatform: HostPlatform.darwin_arm64,
);

void main() {
late Artifacts artifacts;
late String iosDeployPath;
Expand Down Expand Up @@ -153,6 +158,7 @@ void main() {
ProcessManager: () => processManager,
FileSystem: () => fileSystem,
Logger: () => logger,
OperatingSystemUtils: () => os,
Platform: () => macPlatform,
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(buildSettings: const <String, String>{
'WRAPPER_NAME': 'My Super Awesome App.app',
Expand Down Expand Up @@ -243,6 +249,92 @@ void main() {
ProcessManager: () => processManager,
FileSystem: () => fileSystem,
Logger: () => logger,
OperatingSystemUtils: () => os,
Platform: () => macPlatform,
XcodeProjectInterpreter: () => fakeXcodeProjectInterpreter,
Xcode: () => xcode,
});

testUsingContext('ONLY_ACTIVE_ARCH is NO if different host and target architectures', () async {
// Host architecture is x64, target architecture is arm64.
final IOSDevice iosDevice = setUpIOSDevice(
fileSystem: fileSystem,
processManager: processManager,
logger: logger,
artifacts: artifacts,
);
setUpIOSProject(fileSystem);
final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory);
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App');
fileSystem.directory('build/ios/Release-iphoneos/My Super Awesome App.app').createSync(recursive: true);

processManager.addCommand(FakeCommand(command: _xattrArgs(flutterProject)));
processManager.addCommand(const FakeCommand(command: <String>[
'xcrun',
'xcodebuild',
'-configuration',
'Release',
'-quiet',
'-workspace',
'Runner.xcworkspace',
'-scheme',
'Runner',
'BUILD_DIR=/build/ios',
'-sdk',
'iphoneos',
'-destination',
'id=123',
'ONLY_ACTIVE_ARCH=NO',
'ARCHS=arm64',
'-resultBundlePath',
'/.tmp_rand0/flutter_ios_build_temp_dirrand0/temporary_xcresult_bundle',
'-resultBundleVersion',
'3',
'FLUTTER_SUPPRESS_ANALYTICS=true',
'COMPILER_INDEX_STORE_ENABLE=NO',
]));
processManager.addCommand(const FakeCommand(command: <String>[
'rsync',
'-8',
'-av',
'--delete',
'build/ios/Release-iphoneos/My Super Awesome App.app',
'build/ios/iphoneos',
]));
processManager.addCommand(FakeCommand(
command: <String>[
iosDeployPath,
'--id',
'123',
'--bundle',
'build/ios/iphoneos/My Super Awesome App.app',
'--app_deltas',
'build/ios/app-delta',
'--no-wifi',
'--justlaunch',
'--args',
const <String>[
'--enable-dart-profiling',
].join(' '),
])
);

final LaunchResult launchResult = await iosDevice.startApp(
buildableIOSApp,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
platformArgs: <String, Object>{},
);

expect(fileSystem.directory('build/ios/iphoneos'), exists);
expect(launchResult.started, true);
expect(processManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{
ProcessManager: () => processManager,
FileSystem: () => fileSystem,
Logger: () => logger,
OperatingSystemUtils: () => FakeOperatingSystemUtils(
hostPlatform: HostPlatform.darwin_x64,
),
Platform: () => macPlatform,
XcodeProjectInterpreter: () => fakeXcodeProjectInterpreter,
Xcode: () => xcode,
Expand Down Expand Up @@ -362,6 +454,7 @@ void main() {
ProcessManager: () => FakeProcessManager.any(),
FileSystem: () => fileSystem,
Logger: () => logger,
OperatingSystemUtils: () => os,
Platform: () => macPlatform,
XcodeProjectInterpreter: () => fakeXcodeProjectInterpreter,
Xcode: () => xcode,
Expand Down Expand Up @@ -396,6 +489,7 @@ void main() {
ProcessManager: () => FakeProcessManager.any(),
FileSystem: () => fileSystem,
Logger: () => logger,
OperatingSystemUtils: () => os,
Platform: () => macPlatform,
XcodeProjectInterpreter: () => fakeXcodeProjectInterpreter,
Xcode: () => xcode,
Expand Down Expand Up @@ -430,6 +524,7 @@ void main() {
ProcessManager: () => FakeProcessManager.any(),
FileSystem: () => fileSystem,
Logger: () => logger,
OperatingSystemUtils: () => os,
Platform: () => macPlatform,
XcodeProjectInterpreter: () => fakeXcodeProjectInterpreter,
Xcode: () => xcode,
Expand Down Expand Up @@ -465,6 +560,7 @@ void main() {
ProcessManager: () => FakeProcessManager.any(),
FileSystem: () => fileSystem,
Logger: () => logger,
OperatingSystemUtils: () => os,
Platform: () => macPlatform,
XcodeProjectInterpreter: () => fakeXcodeProjectInterpreter,
Xcode: () => xcode,
Expand Down Expand Up @@ -531,6 +627,7 @@ void main() {
ProcessManager: () => FakeProcessManager.any(),
FileSystem: () => fileSystem,
Logger: () => logger,
OperatingSystemUtils: () => os,
Platform: () => macPlatform,
XcodeProjectInterpreter: () => fakeXcodeProjectInterpreter,
Xcode: () => xcode,
Expand Down Expand Up @@ -606,6 +703,7 @@ void main() {
ProcessManager: () => FakeProcessManager.any(),
FileSystem: () => fileSystem,
Logger: () => logger,
OperatingSystemUtils: () => os,
Platform: () => macPlatform,
XcodeProjectInterpreter: () => fakeXcodeProjectInterpreter,
Xcode: () => xcode,
Expand Down Expand Up @@ -686,6 +784,7 @@ void main() {
ProcessManager: () => FakeProcessManager.any(),
FileSystem: () => fileSystem,
Logger: () => logger,
OperatingSystemUtils: () => os,
Platform: () => macPlatform,
XcodeProjectInterpreter: () => fakeXcodeProjectInterpreter,
Xcode: () => xcode,
Expand Down Expand Up @@ -764,6 +863,7 @@ void main() {
ProcessManager: () => FakeProcessManager.any(),
FileSystem: () => fileSystem,
Logger: () => logger,
OperatingSystemUtils: () => os,
Platform: () => macPlatform,
XcodeProjectInterpreter: () => fakeXcodeProjectInterpreter,
Xcode: () => xcode,
Expand Down Expand Up @@ -839,6 +939,7 @@ IOSDevice setUpIOSDevice({
bool isCoreDevice = false,
IOSCoreDeviceControl? coreDeviceControl,
FakeXcodeDebug? xcodeDebug,
DarwinArch cpuArchitecture = DarwinArch.arm64,
}) {
artifacts ??= Artifacts.test();
final Cache cache = Cache.test(
Expand Down Expand Up @@ -872,7 +973,7 @@ IOSDevice setUpIOSDevice({
),
coreDeviceControl: coreDeviceControl ?? FakeIOSCoreDeviceControl(),
xcodeDebug: xcodeDebug ?? FakeXcodeDebug(),
cpuArchitecture: DarwinArch.arm64,
cpuArchitecture: cpuArchitecture,
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
isPaired: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,57 @@ void main() {
expect(context.stderr, isEmpty);
});

test('does not assumes ARCHS if ARCHS and NATIVE_ARCH are different', () {
final Directory buildDir = fileSystem.directory('/path/to/builds')
..createSync(recursive: true);
final Directory flutterRoot = fileSystem.directory('/path/to/flutter')
..createSync(recursive: true);
final File pipe = fileSystem.file('/tmp/pipe')
..createSync(recursive: true);
const String buildMode = 'Debug';
final TestContext context = TestContext(
<String>['prepare'],
<String, String>{
'BUILT_PRODUCTS_DIR': buildDir.path,
'CONFIGURATION': buildMode,
'FLUTTER_ROOT': flutterRoot.path,
'INFOPLIST_PATH': 'Info.plist',
'ARCHS': 'arm64',
'ONLY_ACTIVE_ARCH': 'YES',
'NATIVE_ARCH': 'x86_64',
},
commands: <FakeCommand>[
FakeCommand(
command: <String>[
'${flutterRoot.path}/bin/flutter',
'assemble',
'--no-version-check',
'--output=${buildDir.path}/',
'-dTargetPlatform=ios',
'-dTargetFile=lib/main.dart',
'-dBuildMode=${buildMode.toLowerCase()}',
'-dIosArchs=arm64',
'-dSdkRoot=',
'-dSplitDebugInfo=',
'-dTreeShakeIcons=',
'-dTrackWidgetCreation=',
'-dDartObfuscation=',
'-dAction=',
'-dFrontendServerStarterPath=',
'--ExtraGenSnapshotOptions=',
'--DartDefines=',
'--ExtraFrontEndOptions=',
'-dPreBuildAction=PrepareFramework',
'debug_unpack_ios',
],
),
],
fileSystem: fileSystem,
scriptOutputStreamFile: pipe,
)..run();
expect(context.stderr, isEmpty);
});

test('does not assumes ARCHS if ONLY_ACTIVE_ARCH is not YES', () {
final Directory buildDir = fileSystem.directory('/path/to/builds')
..createSync(recursive: true);
Expand Down