Skip to content

Commit

Permalink
[CP 3.13] Set the CONFIGURATION_BUILD_DIR in generated xcconfig when …
Browse files Browse the repository at this point in the history
…debugging core device (flutter#134824)

Original PR: flutter#134493
  • Loading branch information
vashworth authored Sep 15, 2023
1 parent 367f9ea commit 0776843
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 44 deletions.
11 changes: 11 additions & 0 deletions .ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3868,6 +3868,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_platform_view_ui_tests_ios
recipe: devicelab/devicelab_drone
presubmit: false
Expand Down
1 change: 1 addition & 0 deletions TESTOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,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
Expand Down
21 changes: 21 additions & 0 deletions dev/devicelab/bin/tasks/microbenchmarks_ios_xcode_debug.dart
Original file line number Diff line number Diff line change
@@ -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',
},
));
}
6 changes: 6 additions & 0 deletions dev/devicelab/lib/microbenchmarks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 5 additions & 2 deletions dev/devicelab/lib/tasks/microbenchmarks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -41,9 +44,9 @@ TaskFunction createMicrobenchmarkTask({bool? enableImpeller}) {
return startFlutter(
'run',
options: options,
environment: environment,
);
});

return readJsonResults(flutterProcess);
}

Expand Down
30 changes: 29 additions & 1 deletion packages/flutter_tools/lib/src/ios/devices.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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.');
Expand Down Expand Up @@ -573,6 +573,7 @@ class IOSDevice extends Device {
debuggingOptions: debuggingOptions,
package: package,
launchArguments: launchArguments,
mainPath: mainPath,
discoveryTimeout: discoveryTimeout,
shutdownHooks: shutdownHooks ?? globals.shutdownHooks,
) ? 0 : 1;
Expand Down Expand Up @@ -737,6 +738,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 {
Expand Down Expand Up @@ -775,6 +777,7 @@ class IOSDevice extends Device {
});

XcodeDebugProject debugProject;
final FlutterProject flutterProject = FlutterProject.current();

if (package is PrebuiltIOSApp) {
debugProject = await _xcodeDebug.createXcodeProjectWithCustomBundle(
Expand All @@ -783,6 +786,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) {
Expand Down Expand Up @@ -823,6 +839,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;
}
}
Expand Down
2 changes: 0 additions & 2 deletions packages/flutter_tools/lib/src/ios/mac.dart
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ Future<XcodeBuildResult> buildXcodeProject({
DarwinArch? activeArch,
bool codesign = true,
String? deviceID,
bool isCoreDevice = false,
bool configOnly = false,
XcodeBuildAction buildAction = XcodeBuildAction.build,
}) async {
Expand Down Expand Up @@ -241,7 +240,6 @@ Future<XcodeBuildResult> buildXcodeProject({
project: project,
targetOverride: targetOverride,
buildInfo: buildInfo,
usingCoreDevice: isCoreDevice,
);
await processPodsIfNeeded(project.ios, getIosBuildDirectory(), buildInfo.mode);
if (configOnly) {
Expand Down
13 changes: 7 additions & 6 deletions packages/flutter_tools/lib/src/ios/xcode_build_settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ Future<void> updateGeneratedXcodeProperties({
String? targetOverride,
bool useMacOSConfig = false,
String? buildDirOverride,
bool usingCoreDevice = false,
String? configurationBuildDir,
}) async {
final List<String> xcodeBuildSettings = await _xcodeBuildSettingsLines(
project: project,
buildInfo: buildInfo,
targetOverride: targetOverride,
useMacOSConfig: useMacOSConfig,
buildDirOverride: buildDirOverride,
usingCoreDevice: usingCoreDevice,
configurationBuildDir: configurationBuildDir,
);

_updateGeneratedXcodePropertiesFile(
Expand Down Expand Up @@ -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>[];

Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -750,20 +826,25 @@ class FakeXcodeDebug extends Fake implements XcodeDebug {
this.expectedProject,
this.expectedDeviceId,
this.expectedLaunchArguments,
this.debugStartedCompleter,
this.debugEndedCompleter,
});

final bool debugSuccess;

final XcodeDebugProject? expectedProject;
final String? expectedDeviceId;
final List<String>? expectedLaunchArguments;
final Completer<void>? debugStartedCompleter;
final Completer<void>? debugEndedCompleter;

@override
Future<bool> debugApp({
required XcodeDebugProject project,
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);
Expand All @@ -776,6 +857,7 @@ class FakeXcodeDebug extends Fake implements XcodeDebug {
if (expectedLaunchArguments != null) {
expect(expectedLaunchArguments, launchArguments);
}
await debugEndedCompleter?.future;
return debugSuccess;
}
}
Expand Down
Loading

0 comments on commit 0776843

Please sign in to comment.