Skip to content

Commit 0776843

Browse files
authored
[CP 3.13] Set the CONFIGURATION_BUILD_DIR in generated xcconfig when debugging core device (#134824)
Original PR: flutter/flutter#134493
1 parent 367f9ea commit 0776843

10 files changed

Lines changed: 170 additions & 44 deletions

File tree

.ci.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3868,6 +3868,17 @@ targets:
38683868
["devicelab", "ios", "mac"]
38693869
task_name: microbenchmarks_ios
38703870

3871+
# TODO(vashworth): Remove after Xcode 15 and iOS 17 are in CI (https://github.com/flutter/flutter/issues/132128)
3872+
- name: Mac_ios microbenchmarks_ios_xcode_debug
3873+
recipe: devicelab/devicelab_drone
3874+
presubmit: false
3875+
timeout: 60
3876+
properties:
3877+
tags: >
3878+
["devicelab", "ios", "mac"]
3879+
task_name: microbenchmarks_ios_xcode_debug
3880+
bringup: true
3881+
38713882
- name: Mac_ios native_platform_view_ui_tests_ios
38723883
recipe: devicelab/devicelab_drone
38733884
presubmit: false

TESTOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@
188188
/dev/devicelab/bin/tasks/large_image_changer_perf_ios.dart @zanderso @flutter/engine
189189
/dev/devicelab/bin/tasks/macos_chrome_dev_mode.dart @zanderso @flutter/tool
190190
/dev/devicelab/bin/tasks/microbenchmarks_ios.dart @cyanglaz @flutter/engine
191+
/dev/devicelab/bin/tasks/microbenchmarks_ios_xcode_debug.dart @vashworth @flutter/engine
191192
/dev/devicelab/bin/tasks/native_platform_view_ui_tests_ios.dart @hellohuanlin @flutter/ios
192193
/dev/devicelab/bin/tasks/new_gallery_ios__transition_perf.dart @zanderso @flutter/engine
193194
/dev/devicelab/bin/tasks/new_gallery_skia_ios__transition_perf.dart @zanderso @flutter/engine
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.');
@@ -573,6 +573,7 @@ class IOSDevice extends Device {
573573
debuggingOptions: debuggingOptions,
574574
package: package,
575575
launchArguments: launchArguments,
576+
mainPath: mainPath,
576577
discoveryTimeout: discoveryTimeout,
577578
shutdownHooks: shutdownHooks ?? globals.shutdownHooks,
578579
) ? 0 : 1;
@@ -737,6 +738,7 @@ class IOSDevice extends Device {
737738
required DebuggingOptions debuggingOptions,
738739
required IOSApp package,
739740
required List<String> launchArguments,
741+
required String? mainPath,
740742
required ShutdownHooks shutdownHooks,
741743
@visibleForTesting Duration? discoveryTimeout,
742744
}) async {
@@ -775,6 +777,7 @@ class IOSDevice extends Device {
775777
});
776778

777779
XcodeDebugProject debugProject;
780+
final FlutterProject flutterProject = FlutterProject.current();
778781

779782
if (package is PrebuiltIOSApp) {
780783
debugProject = await _xcodeDebug.createXcodeProjectWithCustomBundle(
@@ -783,6 +786,19 @@ class IOSDevice extends Device {
783786
verboseLogging: _logger.isVerbose,
784787
);
785788
} else if (package is BuildableIOSApp) {
789+
// Before installing/launching/debugging with Xcode, update the build
790+
// settings to use a custom configuration build directory so Xcode
791+
// knows where to find the app bundle to launch.
792+
final Directory bundle = _fileSystem.directory(
793+
package.deviceBundlePath,
794+
);
795+
await updateGeneratedXcodeProperties(
796+
project: flutterProject,
797+
buildInfo: debuggingOptions.buildInfo,
798+
targetOverride: mainPath,
799+
configurationBuildDir: bundle.parent.absolute.path,
800+
);
801+
786802
final IosProject project = package.project;
787803
final XcodeProjectInfo? projectInfo = await project.projectInfo();
788804
if (projectInfo == null) {
@@ -823,6 +839,18 @@ class IOSDevice extends Device {
823839
shutdownHooks.addShutdownHook(() => _xcodeDebug.exit(force: true));
824840
}
825841

842+
if (package is BuildableIOSApp) {
843+
// After automating Xcode, reset the Generated settings to not include
844+
// the custom configuration build directory. This is to prevent
845+
// confusion if the project is later ran via Xcode rather than the
846+
// Flutter CLI.
847+
await updateGeneratedXcodeProperties(
848+
project: flutterProject,
849+
buildInfo: debuggingOptions.buildInfo,
850+
targetOverride: mainPath,
851+
);
852+
}
853+
826854
return debugSuccess;
827855
}
828856
}

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ Future<XcodeBuildResult> buildXcodeProject({
132132
DarwinArch? activeArch,
133133
bool codesign = true,
134134
String? deviceID,
135-
bool isCoreDevice = false,
136135
bool configOnly = false,
137136
XcodeBuildAction buildAction = XcodeBuildAction.build,
138137
}) async {
@@ -241,7 +240,6 @@ Future<XcodeBuildResult> buildXcodeProject({
241240
project: project,
242241
targetOverride: targetOverride,
243242
buildInfo: buildInfo,
244-
usingCoreDevice: isCoreDevice,
245243
);
246244
await processPodsIfNeeded(project.ios, getIosBuildDirectory(), buildInfo.mode);
247245
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)