[CP] Set the CONFIGURATION_BUILD_DIR in generated xcconfig when debugging core device (#134835)
Original PR: https://github.com/flutter/flutter/pull/134493
diff --git a/.ci.yaml b/.ci.yaml
index 67977d1..4cfabf2 100644
--- a/.ci.yaml
+++ b/.ci.yaml
@@ -4071,6 +4071,17 @@
["devicelab", "ios", "mac"]
task_name: microbenchmarks_ios
+ # TODO(vashworth): Remove after Xcode 15 and iOS 17 are in CI (https://github.com/flutter/flutter/issues/132128)
+ - name: Mac_ios microbenchmarks_ios_xcode_debug
+ recipe: devicelab/devicelab_drone
+ presubmit: false
+ timeout: 60
+ properties:
+ tags: >
+ ["devicelab", "ios", "mac"]
+ task_name: microbenchmarks_ios_xcode_debug
+ bringup: true
+
- name: Mac_ios native_platform_view_ui_tests_ios
recipe: devicelab/devicelab_drone
presubmit: false
diff --git a/TESTOWNERS b/TESTOWNERS
index 7da41d6..1b02dde 100644
--- a/TESTOWNERS
+++ b/TESTOWNERS
@@ -199,6 +199,7 @@
/dev/devicelab/bin/tasks/large_image_changer_perf_ios.dart @zanderso @flutter/engine
/dev/devicelab/bin/tasks/macos_chrome_dev_mode.dart @zanderso @flutter/tool
/dev/devicelab/bin/tasks/microbenchmarks_ios.dart @cyanglaz @flutter/engine
+/dev/devicelab/bin/tasks/microbenchmarks_ios_xcode_debug.dart @vashworth @flutter/engine
/dev/devicelab/bin/tasks/native_platform_view_ui_tests_ios.dart @hellohuanlin @flutter/ios
/dev/devicelab/bin/tasks/new_gallery_ios__transition_perf.dart @zanderso @flutter/engine
/dev/devicelab/bin/tasks/new_gallery_skia_ios__transition_perf.dart @zanderso @flutter/engine
diff --git a/dev/devicelab/bin/tasks/microbenchmarks_ios_xcode_debug.dart b/dev/devicelab/bin/tasks/microbenchmarks_ios_xcode_debug.dart
new file mode 100644
index 0000000..3373a68
--- /dev/null
+++ b/dev/devicelab/bin/tasks/microbenchmarks_ios_xcode_debug.dart
@@ -0,0 +1,21 @@
+// 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_devicelab/framework/devices.dart';
+import 'package:flutter_devicelab/framework/framework.dart';
+import 'package:flutter_devicelab/tasks/microbenchmarks.dart';
+
+/// Runs microbenchmarks on iOS.
+Future<void> main() async {
+ // XcodeDebug workflow is used for CoreDevices (iOS 17+ and Xcode 15+). Use
+ // FORCE_XCODE_DEBUG environment variable to force the use of XcodeDebug
+ // workflow in CI to test from older versions since devicelab has not yet been
+ // updated to iOS 17 and Xcode 15.
+ deviceOperatingSystem = DeviceOperatingSystem.ios;
+ await task(createMicrobenchmarkTask(
+ environment: <String, String>{
+ 'FORCE_XCODE_DEBUG': 'true',
+ },
+ ));
+}
diff --git a/dev/devicelab/lib/microbenchmarks.dart b/dev/devicelab/lib/microbenchmarks.dart
index 0cf3d81..451be27 100644
--- a/dev/devicelab/lib/microbenchmarks.dart
+++ b/dev/devicelab/lib/microbenchmarks.dart
@@ -64,6 +64,12 @@
// See https://github.com/flutter/flutter/issues/19208
process.stdin.write('q');
await process.stdin.flush();
+
+ // Give the process a couple of seconds to exit and run shutdown hooks
+ // before sending kill signal.
+ // TODO(fujino): https://github.com/flutter/flutter/issues/134566
+ await Future<void>.delayed(const Duration(seconds: 2));
+
// Also send a kill signal in case the `q` above didn't work.
process.kill(ProcessSignal.sigint);
try {
diff --git a/dev/devicelab/lib/tasks/microbenchmarks.dart b/dev/devicelab/lib/tasks/microbenchmarks.dart
index 967bb58..6bd01aa 100644
--- a/dev/devicelab/lib/tasks/microbenchmarks.dart
+++ b/dev/devicelab/lib/tasks/microbenchmarks.dart
@@ -15,7 +15,10 @@
/// Creates a device lab task that runs benchmarks in
/// `dev/benchmarks/microbenchmarks` reports results to the dashboard.
-TaskFunction createMicrobenchmarkTask({bool? enableImpeller}) {
+TaskFunction createMicrobenchmarkTask({
+ bool? enableImpeller,
+ Map<String, String> environment = const <String, String>{},
+}) {
return () async {
final Device device = await devices.workingDevice;
await device.unlock();
@@ -41,9 +44,9 @@
return startFlutter(
'run',
options: options,
+ environment: environment,
);
});
-
return readJsonResults(flutterProcess);
}
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index 85d89ac..0316576 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -34,6 +34,7 @@
import 'ios_workflow.dart';
import 'iproxy.dart';
import 'mac.dart';
+import 'xcode_build_settings.dart';
import 'xcode_debug.dart';
import 'xcodeproj.dart';
@@ -500,7 +501,6 @@
targetOverride: mainPath,
activeArch: cpuArchitecture,
deviceID: id,
- isCoreDevice: isCoreDevice || forceXcodeDebugWorkflow,
);
if (!buildResult.success) {
_logger.printError('Could not build the precompiled application for the device.');
@@ -573,6 +573,7 @@
debuggingOptions: debuggingOptions,
package: package,
launchArguments: launchArguments,
+ mainPath: mainPath,
discoveryTimeout: discoveryTimeout,
shutdownHooks: shutdownHooks ?? globals.shutdownHooks,
) ? 0 : 1;
@@ -737,6 +738,7 @@
required DebuggingOptions debuggingOptions,
required IOSApp package,
required List<String> launchArguments,
+ required String? mainPath,
required ShutdownHooks shutdownHooks,
@visibleForTesting Duration? discoveryTimeout,
}) async {
@@ -775,6 +777,7 @@
});
XcodeDebugProject debugProject;
+ final FlutterProject flutterProject = FlutterProject.current();
if (package is PrebuiltIOSApp) {
debugProject = await _xcodeDebug.createXcodeProjectWithCustomBundle(
@@ -783,6 +786,19 @@
verboseLogging: _logger.isVerbose,
);
} else if (package is BuildableIOSApp) {
+ // Before installing/launching/debugging with Xcode, update the build
+ // settings to use a custom configuration build directory so Xcode
+ // knows where to find the app bundle to launch.
+ final Directory bundle = _fileSystem.directory(
+ package.deviceBundlePath,
+ );
+ await updateGeneratedXcodeProperties(
+ project: flutterProject,
+ buildInfo: debuggingOptions.buildInfo,
+ targetOverride: mainPath,
+ configurationBuildDir: bundle.parent.absolute.path,
+ );
+
final IosProject project = package.project;
final XcodeProjectInfo? projectInfo = await project.projectInfo();
if (projectInfo == null) {
@@ -823,6 +839,18 @@
shutdownHooks.addShutdownHook(() => _xcodeDebug.exit(force: true));
}
+ if (package is BuildableIOSApp) {
+ // After automating Xcode, reset the Generated settings to not include
+ // the custom configuration build directory. This is to prevent
+ // confusion if the project is later ran via Xcode rather than the
+ // Flutter CLI.
+ await updateGeneratedXcodeProperties(
+ project: flutterProject,
+ buildInfo: debuggingOptions.buildInfo,
+ targetOverride: mainPath,
+ );
+ }
+
return debugSuccess;
}
}
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index f51d436..8819b5c 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -133,7 +133,6 @@
DarwinArch? activeArch,
bool codesign = true,
String? deviceID,
- bool isCoreDevice = false,
bool configOnly = false,
XcodeBuildAction buildAction = XcodeBuildAction.build,
}) async {
@@ -242,7 +241,6 @@
project: project,
targetOverride: targetOverride,
buildInfo: buildInfo,
- usingCoreDevice: isCoreDevice,
);
await processPodsIfNeeded(project.ios, getIosBuildDirectory(), buildInfo.mode);
if (configOnly) {
diff --git a/packages/flutter_tools/lib/src/ios/xcode_build_settings.dart b/packages/flutter_tools/lib/src/ios/xcode_build_settings.dart
index df53b38..8bf662b 100644
--- a/packages/flutter_tools/lib/src/ios/xcode_build_settings.dart
+++ b/packages/flutter_tools/lib/src/ios/xcode_build_settings.dart
@@ -35,7 +35,7 @@
String? targetOverride,
bool useMacOSConfig = false,
String? buildDirOverride,
- bool usingCoreDevice = false,
+ String? configurationBuildDir,
}) async {
final List<String> xcodeBuildSettings = await _xcodeBuildSettingsLines(
project: project,
@@ -43,7 +43,7 @@
targetOverride: targetOverride,
useMacOSConfig: useMacOSConfig,
buildDirOverride: buildDirOverride,
- usingCoreDevice: usingCoreDevice,
+ configurationBuildDir: configurationBuildDir,
);
_updateGeneratedXcodePropertiesFile(
@@ -145,7 +145,7 @@
String? targetOverride,
bool useMacOSConfig = false,
String? buildDirOverride,
- bool usingCoreDevice = false,
+ String? configurationBuildDir,
}) async {
final List<String> xcodeBuildSettings = <String>[];
@@ -174,9 +174,10 @@
xcodeBuildSettings.add('FLUTTER_BUILD_NUMBER=$buildNumber');
// CoreDevices in debug and profile mode are launched, but not built, via Xcode.
- // Set the BUILD_DIR so Xcode knows where to find the app bundle to launch.
- if (usingCoreDevice && !buildInfo.isRelease) {
- xcodeBuildSettings.add('BUILD_DIR=${globals.fs.path.absolute(getIosBuildDirectory())}');
+ // Set the CONFIGURATION_BUILD_DIR so Xcode knows where to find the app
+ // bundle to launch.
+ if (configurationBuildDir != null) {
+ xcodeBuildSettings.add('CONFIGURATION_BUILD_DIR=$configurationBuildDir');
}
final LocalEngineInfo? localEngineInfo = globals.artifacts?.localEngineInfo;
diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart
index d7f354c..00d19d1 100644
--- a/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart
@@ -519,6 +519,82 @@
Xcode: () => xcode,
});
+ testUsingContext('updates Generated.xcconfig before and after launch', () async {
+ final Completer<void> debugStartedCompleter = Completer<void>();
+ final Completer<void> debugEndedCompleter = Completer<void>();
+ final IOSDevice iosDevice = setUpIOSDevice(
+ fileSystem: fileSystem,
+ processManager: FakeProcessManager.any(),
+ logger: logger,
+ artifacts: artifacts,
+ isCoreDevice: true,
+ coreDeviceControl: FakeIOSCoreDeviceControl(),
+ xcodeDebug: FakeXcodeDebug(
+ expectedProject: XcodeDebugProject(
+ scheme: 'Runner',
+ xcodeWorkspace: fileSystem.directory('/ios/Runner.xcworkspace'),
+ xcodeProject: fileSystem.directory('/ios/Runner.xcodeproj'),
+ ),
+ expectedDeviceId: '123',
+ expectedLaunchArguments: <String>['--enable-dart-profiling'],
+ debugStartedCompleter: debugStartedCompleter,
+ debugEndedCompleter: debugEndedCompleter,
+ ),
+ );
+
+ setUpIOSProject(fileSystem);
+ final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory);
+ final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app');
+ fileSystem.directory('build/ios/Release-iphoneos/My Super Awesome App.app').createSync(recursive: true);
+
+ final FakeDeviceLogReader deviceLogReader = FakeDeviceLogReader();
+
+ iosDevice.portForwarder = const NoOpDevicePortForwarder();
+ iosDevice.setLogReader(buildableIOSApp, deviceLogReader);
+
+ // Start writing messages to the log reader.
+ Timer.run(() {
+ deviceLogReader.addLine('Foo');
+ deviceLogReader.addLine('The Dart VM service is listening on http://127.0.0.1:456');
+ });
+
+ final Future<LaunchResult> futureLaunchResult = iosDevice.startApp(
+ buildableIOSApp,
+ debuggingOptions: DebuggingOptions.enabled(const BuildInfo(
+ BuildMode.debug,
+ null,
+ buildName: '1.2.3',
+ buildNumber: '4',
+ treeShakeIcons: false,
+ )),
+ platformArgs: <String, Object>{},
+ );
+
+ await debugStartedCompleter.future;
+
+ // Validate CoreDevice build settings were used
+ final File config = fileSystem.directory('ios').childFile('Flutter/Generated.xcconfig');
+ expect(config.existsSync(), isTrue);
+
+ String contents = config.readAsStringSync();
+ expect(contents, contains('CONFIGURATION_BUILD_DIR=/build/ios/iphoneos'));
+
+ debugEndedCompleter.complete();
+
+ await futureLaunchResult;
+
+ // Validate CoreDevice build settings were removed after launch
+ contents = config.readAsStringSync();
+ expect(contents.contains('CONFIGURATION_BUILD_DIR'), isFalse);
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => FakeProcessManager.any(),
+ FileSystem: () => fileSystem,
+ Logger: () => logger,
+ Platform: () => macPlatform,
+ XcodeProjectInterpreter: () => fakeXcodeProjectInterpreter,
+ Xcode: () => xcode,
+ });
+
testUsingContext('fails when Xcode project is not found', () async {
final IOSDevice iosDevice = setUpIOSDevice(
fileSystem: fileSystem,
@@ -750,6 +826,8 @@
this.expectedProject,
this.expectedDeviceId,
this.expectedLaunchArguments,
+ this.debugStartedCompleter,
+ this.debugEndedCompleter,
});
final bool debugSuccess;
@@ -757,6 +835,8 @@
final XcodeDebugProject? expectedProject;
final String? expectedDeviceId;
final List<String>? expectedLaunchArguments;
+ final Completer<void>? debugStartedCompleter;
+ final Completer<void>? debugEndedCompleter;
@override
Future<bool> debugApp({
@@ -764,6 +844,7 @@
required String deviceId,
required List<String> launchArguments,
}) async {
+ debugStartedCompleter?.complete();
if (expectedProject != null) {
expect(project.scheme, expectedProject!.scheme);
expect(project.xcodeWorkspace.path, expectedProject!.xcodeWorkspace.path);
@@ -776,6 +857,7 @@
if (expectedLaunchArguments != null) {
expect(expectedLaunchArguments, launchArguments);
}
+ await debugEndedCompleter?.future;
return debugSuccess;
}
}
diff --git a/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart b/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart
index 4dacb94..0d5a24e 100644
--- a/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart
@@ -1308,66 +1308,41 @@
});
group('CoreDevice', () {
- testUsingContext('sets BUILD_DIR for core devices in debug mode', () async {
+ testUsingContext('sets CONFIGURATION_BUILD_DIR when configurationBuildDir is set', () async {
const BuildInfo buildInfo = BuildInfo.debug;
final FlutterProject project = FlutterProject.fromDirectoryTest(fs.directory('path/to/project'));
await updateGeneratedXcodeProperties(
project: project,
buildInfo: buildInfo,
- useMacOSConfig: true,
- usingCoreDevice: true,
+ configurationBuildDir: 'path/to/project/build/ios/iphoneos'
);
- final File config = fs.file('path/to/project/macos/Flutter/ephemeral/Flutter-Generated.xcconfig');
+ final File config = fs.file('path/to/project/ios/Flutter/Generated.xcconfig');
expect(config.existsSync(), isTrue);
final String contents = config.readAsStringSync();
- expect(contents, contains('\nBUILD_DIR=/build/ios\n'));
+ expect(contents, contains('CONFIGURATION_BUILD_DIR=path/to/project/build/ios/iphoneos'));
}, overrides: <Type, Generator>{
Artifacts: () => localIosArtifacts,
- Platform: () => macOS,
+ // Platform: () => macOS,
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
});
- testUsingContext('does not set BUILD_DIR for core devices in release mode', () async {
- const BuildInfo buildInfo = BuildInfo.release;
- final FlutterProject project = FlutterProject.fromDirectoryTest(fs.directory('path/to/project'));
- await updateGeneratedXcodeProperties(
- project: project,
- buildInfo: buildInfo,
- useMacOSConfig: true,
- usingCoreDevice: true,
- );
-
- final File config = fs.file('path/to/project/macos/Flutter/ephemeral/Flutter-Generated.xcconfig');
- expect(config.existsSync(), isTrue);
-
- final String contents = config.readAsStringSync();
- expect(contents.contains('\nBUILD_DIR'), isFalse);
- }, overrides: <Type, Generator>{
- Artifacts: () => localIosArtifacts,
- Platform: () => macOS,
- FileSystem: () => fs,
- ProcessManager: () => FakeProcessManager.any(),
- XcodeProjectInterpreter: () => xcodeProjectInterpreter,
- });
-
- testUsingContext('does not set BUILD_DIR for non core devices', () async {
+ testUsingContext('does not set CONFIGURATION_BUILD_DIR when configurationBuildDir is not set', () async {
const BuildInfo buildInfo = BuildInfo.debug;
final FlutterProject project = FlutterProject.fromDirectoryTest(fs.directory('path/to/project'));
await updateGeneratedXcodeProperties(
project: project,
buildInfo: buildInfo,
- useMacOSConfig: true,
);
- final File config = fs.file('path/to/project/macos/Flutter/ephemeral/Flutter-Generated.xcconfig');
+ final File config = fs.file('path/to/project/ios/Flutter/Generated.xcconfig');
expect(config.existsSync(), isTrue);
final String contents = config.readAsStringSync();
- expect(contents.contains('\nBUILD_DIR'), isFalse);
+ expect(contents.contains('CONFIGURATION_BUILD_DIR'), isFalse);
}, overrides: <Type, Generator>{
Artifacts: () => localIosArtifacts,
Platform: () => macOS,