Skip to content

Commit af5ac93

Browse files
authored
Set the CONFIGURATION_BUILD_DIR in generated xcconfig when debugging core device (flutter#134493)
Xcode uses the CONFIGURATION_BUILD_DIR build setting to determine the location of the bundle to build and install. When launching an app via Xcode with the Xcode debug workflow (for iOS 17 physical devices), temporarily set the CONFIGURATION_BUILD_DIR to the location of the bundle so Xcode can find it. Also, added a Xcode Debug version of the `microbenchmarks_ios` integration test since it uses `flutter run --profile` without using `--use-application-binary`. Fixes flutter#134186.
1 parent 4d5a1d9 commit af5ac93

File tree

10 files changed

+170
-44
lines changed

10 files changed

+170
-44
lines changed

.ci.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4069,6 +4069,17 @@ targets:
40694069
["devicelab", "ios", "mac"]
40704070
task_name: microbenchmarks_ios
40714071

4072+
# TODO(vashworth): Remove after Xcode 15 and iOS 17 are in CI (https://github.com/flutter/flutter/issues/132128)
4073+
- name: Mac_ios microbenchmarks_ios_xcode_debug
4074+
recipe: devicelab/devicelab_drone
4075+
presubmit: false
4076+
timeout: 60
4077+
properties:
4078+
tags: >
4079+
["devicelab", "ios", "mac"]
4080+
task_name: microbenchmarks_ios_xcode_debug
4081+
bringup: true
4082+
40724083
- name: Mac_ios native_assets_ios_simulator
40734084
recipe: devicelab/devicelab_drone
40744085
presubmit: false

TESTOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@
199199
/dev/devicelab/bin/tasks/large_image_changer_perf_ios.dart @zanderso @flutter/engine
200200
/dev/devicelab/bin/tasks/macos_chrome_dev_mode.dart @zanderso @flutter/tool
201201
/dev/devicelab/bin/tasks/microbenchmarks_ios.dart @cyanglaz @flutter/engine
202+
/dev/devicelab/bin/tasks/microbenchmarks_ios_xcode_debug.dart @vashworth @flutter/engine
202203
/dev/devicelab/bin/tasks/native_assets_ios_simulator.dart @dacoharkes @flutter/ios
203204
/dev/devicelab/bin/tasks/native_assets_ios.dart @dacoharkes @flutter/ios
204205
/dev/devicelab/bin/tasks/native_platform_view_ui_tests_ios.dart @hellohuanlin @flutter/ios
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter_devicelab/framework/devices.dart';
6+
import 'package:flutter_devicelab/framework/framework.dart';
7+
import 'package:flutter_devicelab/tasks/microbenchmarks.dart';
8+
9+
/// Runs microbenchmarks on iOS.
10+
Future<void> main() async {
11+
// XcodeDebug workflow is used for CoreDevices (iOS 17+ and Xcode 15+). Use
12+
// FORCE_XCODE_DEBUG environment variable to force the use of XcodeDebug
13+
// workflow in CI to test from older versions since devicelab has not yet been
14+
// updated to iOS 17 and Xcode 15.
15+
deviceOperatingSystem = DeviceOperatingSystem.ios;
16+
await task(createMicrobenchmarkTask(
17+
environment: <String, String>{
18+
'FORCE_XCODE_DEBUG': 'true',
19+
},
20+
));
21+
}

dev/devicelab/lib/microbenchmarks.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ Future<Map<String, double>> readJsonResults(Process process) {
6464
// See https://github.com/flutter/flutter/issues/19208
6565
process.stdin.write('q');
6666
await process.stdin.flush();
67+
68+
// Give the process a couple of seconds to exit and run shutdown hooks
69+
// before sending kill signal.
70+
// TODO(fujino): https://github.com/flutter/flutter/issues/134566
71+
await Future<void>.delayed(const Duration(seconds: 2));
72+
6773
// Also send a kill signal in case the `q` above didn't work.
6874
process.kill(ProcessSignal.sigint);
6975
try {

dev/devicelab/lib/tasks/microbenchmarks.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ import '../microbenchmarks.dart';
1515

1616
/// Creates a device lab task that runs benchmarks in
1717
/// `dev/benchmarks/microbenchmarks` reports results to the dashboard.
18-
TaskFunction createMicrobenchmarkTask({bool? enableImpeller}) {
18+
TaskFunction createMicrobenchmarkTask({
19+
bool? enableImpeller,
20+
Map<String, String> environment = const <String, String>{},
21+
}) {
1922
return () async {
2023
final Device device = await devices.workingDevice;
2124
await device.unlock();
@@ -41,9 +44,9 @@ TaskFunction createMicrobenchmarkTask({bool? enableImpeller}) {
4144
return startFlutter(
4245
'run',
4346
options: options,
47+
environment: environment,
4448
);
4549
});
46-
4750
return readJsonResults(flutterProcess);
4851
}
4952

packages/flutter_tools/lib/src/ios/devices.dart

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import 'ios_deploy.dart';
3434
import 'ios_workflow.dart';
3535
import 'iproxy.dart';
3636
import 'mac.dart';
37+
import 'xcode_build_settings.dart';
3738
import 'xcode_debug.dart';
3839
import 'xcodeproj.dart';
3940

@@ -500,7 +501,6 @@ class IOSDevice extends Device {
500501
targetOverride: mainPath,
501502
activeArch: cpuArchitecture,
502503
deviceID: id,
503-
isCoreDevice: isCoreDevice || forceXcodeDebugWorkflow,
504504
);
505505
if (!buildResult.success) {
506506
_logger.printError('Could not build the precompiled application for the device.');
@@ -551,6 +551,7 @@ class IOSDevice extends Device {
551551
debuggingOptions: debuggingOptions,
552552
package: package,
553553
launchArguments: launchArguments,
554+
mainPath: mainPath,
554555
discoveryTimeout: discoveryTimeout,
555556
shutdownHooks: shutdownHooks ?? globals.shutdownHooks,
556557
) ? 0 : 1;
@@ -784,6 +785,7 @@ class IOSDevice extends Device {
784785
required DebuggingOptions debuggingOptions,
785786
required IOSApp package,
786787
required List<String> launchArguments,
788+
required String? mainPath,
787789
required ShutdownHooks shutdownHooks,
788790
@visibleForTesting Duration? discoveryTimeout,
789791
}) async {
@@ -822,6 +824,7 @@ class IOSDevice extends Device {
822824
});
823825

824826
XcodeDebugProject debugProject;
827+
final FlutterProject flutterProject = FlutterProject.current();
825828

826829
if (package is PrebuiltIOSApp) {
827830
debugProject = await _xcodeDebug.createXcodeProjectWithCustomBundle(
@@ -830,6 +833,19 @@ class IOSDevice extends Device {
830833
verboseLogging: _logger.isVerbose,
831834
);
832835
} else if (package is BuildableIOSApp) {
836+
// Before installing/launching/debugging with Xcode, update the build
837+
// settings to use a custom configuration build directory so Xcode
838+
// knows where to find the app bundle to launch.
839+
final Directory bundle = _fileSystem.directory(
840+
package.deviceBundlePath,
841+
);
842+
await updateGeneratedXcodeProperties(
843+
project: flutterProject,
844+
buildInfo: debuggingOptions.buildInfo,
845+
targetOverride: mainPath,
846+
configurationBuildDir: bundle.parent.absolute.path,
847+
);
848+
833849
final IosProject project = package.project;
834850
final XcodeProjectInfo? projectInfo = await project.projectInfo();
835851
if (projectInfo == null) {
@@ -870,6 +886,18 @@ class IOSDevice extends Device {
870886
shutdownHooks.addShutdownHook(() => _xcodeDebug.exit(force: true));
871887
}
872888

889+
if (package is BuildableIOSApp) {
890+
// After automating Xcode, reset the Generated settings to not include
891+
// the custom configuration build directory. This is to prevent
892+
// confusion if the project is later ran via Xcode rather than the
893+
// Flutter CLI.
894+
await updateGeneratedXcodeProperties(
895+
project: flutterProject,
896+
buildInfo: debuggingOptions.buildInfo,
897+
targetOverride: mainPath,
898+
);
899+
}
900+
873901
return debugSuccess;
874902
}
875903
}

packages/flutter_tools/lib/src/ios/mac.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ Future<XcodeBuildResult> buildXcodeProject({
133133
DarwinArch? activeArch,
134134
bool codesign = true,
135135
String? deviceID,
136-
bool isCoreDevice = false,
137136
bool configOnly = false,
138137
XcodeBuildAction buildAction = XcodeBuildAction.build,
139138
}) async {
@@ -242,7 +241,6 @@ Future<XcodeBuildResult> buildXcodeProject({
242241
project: project,
243242
targetOverride: targetOverride,
244243
buildInfo: buildInfo,
245-
usingCoreDevice: isCoreDevice,
246244
);
247245
await processPodsIfNeeded(project.ios, getIosBuildDirectory(), buildInfo.mode);
248246
if (configOnly) {

packages/flutter_tools/lib/src/ios/xcode_build_settings.dart

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@ Future<void> updateGeneratedXcodeProperties({
3535
String? targetOverride,
3636
bool useMacOSConfig = false,
3737
String? buildDirOverride,
38-
bool usingCoreDevice = false,
38+
String? configurationBuildDir,
3939
}) async {
4040
final List<String> xcodeBuildSettings = await _xcodeBuildSettingsLines(
4141
project: project,
4242
buildInfo: buildInfo,
4343
targetOverride: targetOverride,
4444
useMacOSConfig: useMacOSConfig,
4545
buildDirOverride: buildDirOverride,
46-
usingCoreDevice: usingCoreDevice,
46+
configurationBuildDir: configurationBuildDir,
4747
);
4848

4949
_updateGeneratedXcodePropertiesFile(
@@ -145,7 +145,7 @@ Future<List<String>> _xcodeBuildSettingsLines({
145145
String? targetOverride,
146146
bool useMacOSConfig = false,
147147
String? buildDirOverride,
148-
bool usingCoreDevice = false,
148+
String? configurationBuildDir,
149149
}) async {
150150
final List<String> xcodeBuildSettings = <String>[];
151151

@@ -174,9 +174,10 @@ Future<List<String>> _xcodeBuildSettingsLines({
174174
xcodeBuildSettings.add('FLUTTER_BUILD_NUMBER=$buildNumber');
175175

176176
// CoreDevices in debug and profile mode are launched, but not built, via Xcode.
177-
// Set the BUILD_DIR so Xcode knows where to find the app bundle to launch.
178-
if (usingCoreDevice && !buildInfo.isRelease) {
179-
xcodeBuildSettings.add('BUILD_DIR=${globals.fs.path.absolute(getIosBuildDirectory())}');
177+
// Set the CONFIGURATION_BUILD_DIR so Xcode knows where to find the app
178+
// bundle to launch.
179+
if (configurationBuildDir != null) {
180+
xcodeBuildSettings.add('CONFIGURATION_BUILD_DIR=$configurationBuildDir');
180181
}
181182

182183
final LocalEngineInfo? localEngineInfo = globals.artifacts?.localEngineInfo;

packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,82 @@ void main() {
519519
Xcode: () => xcode,
520520
});
521521

522+
testUsingContext('updates Generated.xcconfig before and after launch', () async {
523+
final Completer<void> debugStartedCompleter = Completer<void>();
524+
final Completer<void> debugEndedCompleter = Completer<void>();
525+
final IOSDevice iosDevice = setUpIOSDevice(
526+
fileSystem: fileSystem,
527+
processManager: FakeProcessManager.any(),
528+
logger: logger,
529+
artifacts: artifacts,
530+
isCoreDevice: true,
531+
coreDeviceControl: FakeIOSCoreDeviceControl(),
532+
xcodeDebug: FakeXcodeDebug(
533+
expectedProject: XcodeDebugProject(
534+
scheme: 'Runner',
535+
xcodeWorkspace: fileSystem.directory('/ios/Runner.xcworkspace'),
536+
xcodeProject: fileSystem.directory('/ios/Runner.xcodeproj'),
537+
),
538+
expectedDeviceId: '123',
539+
expectedLaunchArguments: <String>['--enable-dart-profiling'],
540+
debugStartedCompleter: debugStartedCompleter,
541+
debugEndedCompleter: debugEndedCompleter,
542+
),
543+
);
544+
545+
setUpIOSProject(fileSystem);
546+
final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory);
547+
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app');
548+
fileSystem.directory('build/ios/Release-iphoneos/My Super Awesome App.app').createSync(recursive: true);
549+
550+
final FakeDeviceLogReader deviceLogReader = FakeDeviceLogReader();
551+
552+
iosDevice.portForwarder = const NoOpDevicePortForwarder();
553+
iosDevice.setLogReader(buildableIOSApp, deviceLogReader);
554+
555+
// Start writing messages to the log reader.
556+
Timer.run(() {
557+
deviceLogReader.addLine('Foo');
558+
deviceLogReader.addLine('The Dart VM service is listening on http://127.0.0.1:456');
559+
});
560+
561+
final Future<LaunchResult> futureLaunchResult = iosDevice.startApp(
562+
buildableIOSApp,
563+
debuggingOptions: DebuggingOptions.enabled(const BuildInfo(
564+
BuildMode.debug,
565+
null,
566+
buildName: '1.2.3',
567+
buildNumber: '4',
568+
treeShakeIcons: false,
569+
)),
570+
platformArgs: <String, Object>{},
571+
);
572+
573+
await debugStartedCompleter.future;
574+
575+
// Validate CoreDevice build settings were used
576+
final File config = fileSystem.directory('ios').childFile('Flutter/Generated.xcconfig');
577+
expect(config.existsSync(), isTrue);
578+
579+
String contents = config.readAsStringSync();
580+
expect(contents, contains('CONFIGURATION_BUILD_DIR=/build/ios/iphoneos'));
581+
582+
debugEndedCompleter.complete();
583+
584+
await futureLaunchResult;
585+
586+
// Validate CoreDevice build settings were removed after launch
587+
contents = config.readAsStringSync();
588+
expect(contents.contains('CONFIGURATION_BUILD_DIR'), isFalse);
589+
}, overrides: <Type, Generator>{
590+
ProcessManager: () => FakeProcessManager.any(),
591+
FileSystem: () => fileSystem,
592+
Logger: () => logger,
593+
Platform: () => macPlatform,
594+
XcodeProjectInterpreter: () => fakeXcodeProjectInterpreter,
595+
Xcode: () => xcode,
596+
});
597+
522598
testUsingContext('fails when Xcode project is not found', () async {
523599
final IOSDevice iosDevice = setUpIOSDevice(
524600
fileSystem: fileSystem,
@@ -750,20 +826,25 @@ class FakeXcodeDebug extends Fake implements XcodeDebug {
750826
this.expectedProject,
751827
this.expectedDeviceId,
752828
this.expectedLaunchArguments,
829+
this.debugStartedCompleter,
830+
this.debugEndedCompleter,
753831
});
754832

755833
final bool debugSuccess;
756834

757835
final XcodeDebugProject? expectedProject;
758836
final String? expectedDeviceId;
759837
final List<String>? expectedLaunchArguments;
838+
final Completer<void>? debugStartedCompleter;
839+
final Completer<void>? debugEndedCompleter;
760840

761841
@override
762842
Future<bool> debugApp({
763843
required XcodeDebugProject project,
764844
required String deviceId,
765845
required List<String> launchArguments,
766846
}) async {
847+
debugStartedCompleter?.complete();
767848
if (expectedProject != null) {
768849
expect(project.scheme, expectedProject!.scheme);
769850
expect(project.xcodeWorkspace.path, expectedProject!.xcodeWorkspace.path);
@@ -776,6 +857,7 @@ class FakeXcodeDebug extends Fake implements XcodeDebug {
776857
if (expectedLaunchArguments != null) {
777858
expect(expectedLaunchArguments, launchArguments);
778859
}
860+
await debugEndedCompleter?.future;
779861
return debugSuccess;
780862
}
781863
}

0 commit comments

Comments
 (0)