Unverified Commit af5ac930 authored by Victoria Ashworth's avatar Victoria Ashworth Committed by GitHub

Set the CONFIGURATION_BUILD_DIR in generated xcconfig when debugging core device (#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 https://github.com/flutter/flutter/issues/134186.
parent 4d5a1d91
...@@ -4069,6 +4069,17 @@ targets: ...@@ -4069,6 +4069,17 @@ targets:
["devicelab", "ios", "mac"] ["devicelab", "ios", "mac"]
task_name: microbenchmarks_ios 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_assets_ios_simulator - name: Mac_ios native_assets_ios_simulator
recipe: devicelab/devicelab_drone recipe: devicelab/devicelab_drone
presubmit: false presubmit: false
......
...@@ -199,6 +199,7 @@ ...@@ -199,6 +199,7 @@
/dev/devicelab/bin/tasks/large_image_changer_perf_ios.dart @zanderso @flutter/engine /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/macos_chrome_dev_mode.dart @zanderso @flutter/tool
/dev/devicelab/bin/tasks/microbenchmarks_ios.dart @cyanglaz @flutter/engine /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_assets_ios_simulator.dart @dacoharkes @flutter/ios /dev/devicelab/bin/tasks/native_assets_ios_simulator.dart @dacoharkes @flutter/ios
/dev/devicelab/bin/tasks/native_assets_ios.dart @dacoharkes @flutter/ios /dev/devicelab/bin/tasks/native_assets_ios.dart @dacoharkes @flutter/ios
/dev/devicelab/bin/tasks/native_platform_view_ui_tests_ios.dart @hellohuanlin @flutter/ios /dev/devicelab/bin/tasks/native_platform_view_ui_tests_ios.dart @hellohuanlin @flutter/ios
......
// 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',
},
));
}
...@@ -64,6 +64,12 @@ Future<Map<String, double>> readJsonResults(Process process) { ...@@ -64,6 +64,12 @@ Future<Map<String, double>> readJsonResults(Process process) {
// See https://github.com/flutter/flutter/issues/19208 // See https://github.com/flutter/flutter/issues/19208
process.stdin.write('q'); process.stdin.write('q');
await process.stdin.flush(); 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. // Also send a kill signal in case the `q` above didn't work.
process.kill(ProcessSignal.sigint); process.kill(ProcessSignal.sigint);
try { try {
......
...@@ -15,7 +15,10 @@ import '../microbenchmarks.dart'; ...@@ -15,7 +15,10 @@ import '../microbenchmarks.dart';
/// Creates a device lab task that runs benchmarks in /// Creates a device lab task that runs benchmarks in
/// `dev/benchmarks/microbenchmarks` reports results to the dashboard. /// `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 { return () async {
final Device device = await devices.workingDevice; final Device device = await devices.workingDevice;
await device.unlock(); await device.unlock();
...@@ -41,9 +44,9 @@ TaskFunction createMicrobenchmarkTask({bool? enableImpeller}) { ...@@ -41,9 +44,9 @@ TaskFunction createMicrobenchmarkTask({bool? enableImpeller}) {
return startFlutter( return startFlutter(
'run', 'run',
options: options, options: options,
environment: environment,
); );
}); });
return readJsonResults(flutterProcess); return readJsonResults(flutterProcess);
} }
......
...@@ -34,6 +34,7 @@ import 'ios_deploy.dart'; ...@@ -34,6 +34,7 @@ import 'ios_deploy.dart';
import 'ios_workflow.dart'; import 'ios_workflow.dart';
import 'iproxy.dart'; import 'iproxy.dart';
import 'mac.dart'; import 'mac.dart';
import 'xcode_build_settings.dart';
import 'xcode_debug.dart'; import 'xcode_debug.dart';
import 'xcodeproj.dart'; import 'xcodeproj.dart';
...@@ -500,7 +501,6 @@ class IOSDevice extends Device { ...@@ -500,7 +501,6 @@ class IOSDevice extends Device {
targetOverride: mainPath, targetOverride: mainPath,
activeArch: cpuArchitecture, activeArch: cpuArchitecture,
deviceID: id, deviceID: id,
isCoreDevice: isCoreDevice || forceXcodeDebugWorkflow,
); );
if (!buildResult.success) { if (!buildResult.success) {
_logger.printError('Could not build the precompiled application for the device.'); _logger.printError('Could not build the precompiled application for the device.');
...@@ -551,6 +551,7 @@ class IOSDevice extends Device { ...@@ -551,6 +551,7 @@ class IOSDevice extends Device {
debuggingOptions: debuggingOptions, debuggingOptions: debuggingOptions,
package: package, package: package,
launchArguments: launchArguments, launchArguments: launchArguments,
mainPath: mainPath,
discoveryTimeout: discoveryTimeout, discoveryTimeout: discoveryTimeout,
shutdownHooks: shutdownHooks ?? globals.shutdownHooks, shutdownHooks: shutdownHooks ?? globals.shutdownHooks,
) ? 0 : 1; ) ? 0 : 1;
...@@ -784,6 +785,7 @@ class IOSDevice extends Device { ...@@ -784,6 +785,7 @@ class IOSDevice extends Device {
required DebuggingOptions debuggingOptions, required DebuggingOptions debuggingOptions,
required IOSApp package, required IOSApp package,
required List<String> launchArguments, required List<String> launchArguments,
required String? mainPath,
required ShutdownHooks shutdownHooks, required ShutdownHooks shutdownHooks,
@visibleForTesting Duration? discoveryTimeout, @visibleForTesting Duration? discoveryTimeout,
}) async { }) async {
...@@ -822,6 +824,7 @@ class IOSDevice extends Device { ...@@ -822,6 +824,7 @@ class IOSDevice extends Device {
}); });
XcodeDebugProject debugProject; XcodeDebugProject debugProject;
final FlutterProject flutterProject = FlutterProject.current();
if (package is PrebuiltIOSApp) { if (package is PrebuiltIOSApp) {
debugProject = await _xcodeDebug.createXcodeProjectWithCustomBundle( debugProject = await _xcodeDebug.createXcodeProjectWithCustomBundle(
...@@ -830,6 +833,19 @@ class IOSDevice extends Device { ...@@ -830,6 +833,19 @@ class IOSDevice extends Device {
verboseLogging: _logger.isVerbose, verboseLogging: _logger.isVerbose,
); );
} else if (package is BuildableIOSApp) { } 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 IosProject project = package.project;
final XcodeProjectInfo? projectInfo = await project.projectInfo(); final XcodeProjectInfo? projectInfo = await project.projectInfo();
if (projectInfo == null) { if (projectInfo == null) {
...@@ -870,6 +886,18 @@ class IOSDevice extends Device { ...@@ -870,6 +886,18 @@ class IOSDevice extends Device {
shutdownHooks.addShutdownHook(() => _xcodeDebug.exit(force: true)); 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; return debugSuccess;
} }
} }
......
...@@ -133,7 +133,6 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -133,7 +133,6 @@ Future<XcodeBuildResult> buildXcodeProject({
DarwinArch? activeArch, DarwinArch? activeArch,
bool codesign = true, bool codesign = true,
String? deviceID, String? deviceID,
bool isCoreDevice = false,
bool configOnly = false, bool configOnly = false,
XcodeBuildAction buildAction = XcodeBuildAction.build, XcodeBuildAction buildAction = XcodeBuildAction.build,
}) async { }) async {
...@@ -242,7 +241,6 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -242,7 +241,6 @@ Future<XcodeBuildResult> buildXcodeProject({
project: project, project: project,
targetOverride: targetOverride, targetOverride: targetOverride,
buildInfo: buildInfo, buildInfo: buildInfo,
usingCoreDevice: isCoreDevice,
); );
await processPodsIfNeeded(project.ios, getIosBuildDirectory(), buildInfo.mode); await processPodsIfNeeded(project.ios, getIosBuildDirectory(), buildInfo.mode);
if (configOnly) { if (configOnly) {
......
...@@ -35,7 +35,7 @@ Future<void> updateGeneratedXcodeProperties({ ...@@ -35,7 +35,7 @@ Future<void> updateGeneratedXcodeProperties({
String? targetOverride, String? targetOverride,
bool useMacOSConfig = false, bool useMacOSConfig = false,
String? buildDirOverride, String? buildDirOverride,
bool usingCoreDevice = false, String? configurationBuildDir,
}) async { }) async {
final List<String> xcodeBuildSettings = await _xcodeBuildSettingsLines( final List<String> xcodeBuildSettings = await _xcodeBuildSettingsLines(
project: project, project: project,
...@@ -43,7 +43,7 @@ Future<void> updateGeneratedXcodeProperties({ ...@@ -43,7 +43,7 @@ Future<void> updateGeneratedXcodeProperties({
targetOverride: targetOverride, targetOverride: targetOverride,
useMacOSConfig: useMacOSConfig, useMacOSConfig: useMacOSConfig,
buildDirOverride: buildDirOverride, buildDirOverride: buildDirOverride,
usingCoreDevice: usingCoreDevice, configurationBuildDir: configurationBuildDir,
); );
_updateGeneratedXcodePropertiesFile( _updateGeneratedXcodePropertiesFile(
...@@ -145,7 +145,7 @@ Future<List<String>> _xcodeBuildSettingsLines({ ...@@ -145,7 +145,7 @@ Future<List<String>> _xcodeBuildSettingsLines({
String? targetOverride, String? targetOverride,
bool useMacOSConfig = false, bool useMacOSConfig = false,
String? buildDirOverride, String? buildDirOverride,
bool usingCoreDevice = false, String? configurationBuildDir,
}) async { }) async {
final List<String> xcodeBuildSettings = <String>[]; final List<String> xcodeBuildSettings = <String>[];
...@@ -174,9 +174,10 @@ Future<List<String>> _xcodeBuildSettingsLines({ ...@@ -174,9 +174,10 @@ Future<List<String>> _xcodeBuildSettingsLines({
xcodeBuildSettings.add('FLUTTER_BUILD_NUMBER=$buildNumber'); xcodeBuildSettings.add('FLUTTER_BUILD_NUMBER=$buildNumber');
// CoreDevices in debug and profile mode are launched, but not built, via Xcode. // 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. // Set the CONFIGURATION_BUILD_DIR so Xcode knows where to find the app
if (usingCoreDevice && !buildInfo.isRelease) { // bundle to launch.
xcodeBuildSettings.add('BUILD_DIR=${globals.fs.path.absolute(getIosBuildDirectory())}'); if (configurationBuildDir != null) {
xcodeBuildSettings.add('CONFIGURATION_BUILD_DIR=$configurationBuildDir');
} }
final LocalEngineInfo? localEngineInfo = globals.artifacts?.localEngineInfo; final LocalEngineInfo? localEngineInfo = globals.artifacts?.localEngineInfo;
......
...@@ -519,6 +519,82 @@ void main() { ...@@ -519,6 +519,82 @@ void main() {
Xcode: () => xcode, 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 { testUsingContext('fails when Xcode project is not found', () async {
final IOSDevice iosDevice = setUpIOSDevice( final IOSDevice iosDevice = setUpIOSDevice(
fileSystem: fileSystem, fileSystem: fileSystem,
...@@ -750,6 +826,8 @@ class FakeXcodeDebug extends Fake implements XcodeDebug { ...@@ -750,6 +826,8 @@ class FakeXcodeDebug extends Fake implements XcodeDebug {
this.expectedProject, this.expectedProject,
this.expectedDeviceId, this.expectedDeviceId,
this.expectedLaunchArguments, this.expectedLaunchArguments,
this.debugStartedCompleter,
this.debugEndedCompleter,
}); });
final bool debugSuccess; final bool debugSuccess;
...@@ -757,6 +835,8 @@ class FakeXcodeDebug extends Fake implements XcodeDebug { ...@@ -757,6 +835,8 @@ class FakeXcodeDebug extends Fake implements XcodeDebug {
final XcodeDebugProject? expectedProject; final XcodeDebugProject? expectedProject;
final String? expectedDeviceId; final String? expectedDeviceId;
final List<String>? expectedLaunchArguments; final List<String>? expectedLaunchArguments;
final Completer<void>? debugStartedCompleter;
final Completer<void>? debugEndedCompleter;
@override @override
Future<bool> debugApp({ Future<bool> debugApp({
...@@ -764,6 +844,7 @@ class FakeXcodeDebug extends Fake implements XcodeDebug { ...@@ -764,6 +844,7 @@ class FakeXcodeDebug extends Fake implements XcodeDebug {
required String deviceId, required String deviceId,
required List<String> launchArguments, required List<String> launchArguments,
}) async { }) async {
debugStartedCompleter?.complete();
if (expectedProject != null) { if (expectedProject != null) {
expect(project.scheme, expectedProject!.scheme); expect(project.scheme, expectedProject!.scheme);
expect(project.xcodeWorkspace.path, expectedProject!.xcodeWorkspace.path); expect(project.xcodeWorkspace.path, expectedProject!.xcodeWorkspace.path);
...@@ -776,6 +857,7 @@ class FakeXcodeDebug extends Fake implements XcodeDebug { ...@@ -776,6 +857,7 @@ class FakeXcodeDebug extends Fake implements XcodeDebug {
if (expectedLaunchArguments != null) { if (expectedLaunchArguments != null) {
expect(expectedLaunchArguments, launchArguments); expect(expectedLaunchArguments, launchArguments);
} }
await debugEndedCompleter?.future;
return debugSuccess; return debugSuccess;
} }
} }
......
...@@ -1308,66 +1308,41 @@ flutter: ...@@ -1308,66 +1308,41 @@ flutter:
}); });
group('CoreDevice', () { 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; const BuildInfo buildInfo = BuildInfo.debug;
final FlutterProject project = FlutterProject.fromDirectoryTest(fs.directory('path/to/project')); final FlutterProject project = FlutterProject.fromDirectoryTest(fs.directory('path/to/project'));
await updateGeneratedXcodeProperties( await updateGeneratedXcodeProperties(
project: project, project: project,
buildInfo: buildInfo, buildInfo: buildInfo,
useMacOSConfig: true, configurationBuildDir: 'path/to/project/build/ios/iphoneos'
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=/build/ios\n'));
}, overrides: <Type, Generator>{
Artifacts: () => localIosArtifacts,
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'); final File config = fs.file('path/to/project/ios/Flutter/Generated.xcconfig');
expect(config.existsSync(), isTrue); expect(config.existsSync(), isTrue);
final String contents = config.readAsStringSync(); final String contents = config.readAsStringSync();
expect(contents.contains('\nBUILD_DIR'), isFalse); expect(contents, contains('CONFIGURATION_BUILD_DIR=path/to/project/build/ios/iphoneos'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Artifacts: () => localIosArtifacts, Artifacts: () => localIosArtifacts,
Platform: () => macOS, // Platform: () => macOS,
FileSystem: () => fs, FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
XcodeProjectInterpreter: () => xcodeProjectInterpreter, 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; const BuildInfo buildInfo = BuildInfo.debug;
final FlutterProject project = FlutterProject.fromDirectoryTest(fs.directory('path/to/project')); final FlutterProject project = FlutterProject.fromDirectoryTest(fs.directory('path/to/project'));
await updateGeneratedXcodeProperties( await updateGeneratedXcodeProperties(
project: project, project: project,
buildInfo: buildInfo, 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); expect(config.existsSync(), isTrue);
final String contents = config.readAsStringSync(); final String contents = config.readAsStringSync();
expect(contents.contains('\nBUILD_DIR'), isFalse); expect(contents.contains('CONFIGURATION_BUILD_DIR'), isFalse);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Artifacts: () => localIosArtifacts, Artifacts: () => localIosArtifacts,
Platform: () => macOS, Platform: () => macOS,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment