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:
["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_assets_ios_simulator
recipe: devicelab/devicelab_drone
presubmit: false
......
......@@ -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_assets_ios_simulator.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
......
// 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) {
// 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 {
......
......@@ -15,7 +15,10 @@ import '../microbenchmarks.dart';
/// 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 @@ TaskFunction createMicrobenchmarkTask({bool? enableImpeller}) {
return startFlutter(
'run',
options: options,
environment: environment,
);
});
return readJsonResults(flutterProcess);
}
......
......@@ -34,6 +34,7 @@ import 'ios_deploy.dart';
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 @@ class IOSDevice extends Device {
targetOverride: mainPath,
activeArch: cpuArchitecture,
deviceID: id,
isCoreDevice: isCoreDevice || forceXcodeDebugWorkflow,
);
if (!buildResult.success) {
_logger.printError('Could not build the precompiled application for the device.');
......@@ -551,6 +551,7 @@ class IOSDevice extends Device {
debuggingOptions: debuggingOptions,
package: package,
launchArguments: launchArguments,
mainPath: mainPath,
discoveryTimeout: discoveryTimeout,
shutdownHooks: shutdownHooks ?? globals.shutdownHooks,
) ? 0 : 1;
......@@ -784,6 +785,7 @@ class IOSDevice extends Device {
required DebuggingOptions debuggingOptions,
required IOSApp package,
required List<String> launchArguments,
required String? mainPath,
required ShutdownHooks shutdownHooks,
@visibleForTesting Duration? discoveryTimeout,
}) async {
......@@ -822,6 +824,7 @@ class IOSDevice extends Device {
});
XcodeDebugProject debugProject;
final FlutterProject flutterProject = FlutterProject.current();
if (package is PrebuiltIOSApp) {
debugProject = await _xcodeDebug.createXcodeProjectWithCustomBundle(
......@@ -830,6 +833,19 @@ class IOSDevice extends Device {
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) {
......@@ -870,6 +886,18 @@ class IOSDevice extends Device {
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;
}
}
......
......@@ -133,7 +133,6 @@ Future<XcodeBuildResult> buildXcodeProject({
DarwinArch? activeArch,
bool codesign = true,
String? deviceID,
bool isCoreDevice = false,
bool configOnly = false,
XcodeBuildAction buildAction = XcodeBuildAction.build,
}) async {
......@@ -242,7 +241,6 @@ Future<XcodeBuildResult> buildXcodeProject({
project: project,
targetOverride: targetOverride,
buildInfo: buildInfo,
usingCoreDevice: isCoreDevice,
);
await processPodsIfNeeded(project.ios, getIosBuildDirectory(), buildInfo.mode);
if (configOnly) {
......
......@@ -35,7 +35,7 @@ Future<void> updateGeneratedXcodeProperties({
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 @@ Future<void> updateGeneratedXcodeProperties({
targetOverride: targetOverride,
useMacOSConfig: useMacOSConfig,
buildDirOverride: buildDirOverride,
usingCoreDevice: usingCoreDevice,
configurationBuildDir: configurationBuildDir,
);
_updateGeneratedXcodePropertiesFile(
......@@ -145,7 +145,7 @@ Future<List<String>> _xcodeBuildSettingsLines({
String? targetOverride,
bool useMacOSConfig = false,
String? buildDirOverride,
bool usingCoreDevice = false,
String? configurationBuildDir,
}) async {
final List<String> xcodeBuildSettings = <String>[];
......@@ -174,9 +174,10 @@ Future<List<String>> _xcodeBuildSettingsLines({
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;
......
......@@ -519,6 +519,82 @@ void main() {
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 @@ class FakeXcodeDebug extends Fake implements XcodeDebug {
this.expectedProject,
this.expectedDeviceId,
this.expectedLaunchArguments,
this.debugStartedCompleter,
this.debugEndedCompleter,
});
final bool debugSuccess;
......@@ -757,6 +835,8 @@ class FakeXcodeDebug extends Fake implements XcodeDebug {
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 @@ class FakeXcodeDebug extends Fake implements XcodeDebug {
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 @@ class FakeXcodeDebug extends Fake implements XcodeDebug {
if (expectedLaunchArguments != null) {
expect(expectedLaunchArguments, launchArguments);
}
await debugEndedCompleter?.future;
return debugSuccess;
}
}
......
......@@ -1308,66 +1308,41 @@ flutter:
});
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,
);
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,
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'), isFalse);
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 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,
......
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