mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Ensure Xcode project is setup to start debugger (#136977)
Some users have their Xcode settings set to not debug (see example here https://github.com/flutter/flutter/issues/136197#issuecomment-1766834195). This will cause the [engine check for a debugger](22ce5c6a45/runtime/ptrace_check.cc (L56-L71)
) to fail, which will cause an error and cause the app to crash.
This PR parses the scheme file to ensure the scheme is set to start a debugger and warn the user if it's not.
Fixes https://github.com/flutter/flutter/issues/136197.
This commit is contained in:
parent
9366170e84
commit
5dd2a4e0aa
10
.ci.yaml
10
.ci.yaml
@ -3926,6 +3926,16 @@ targets:
|
|||||||
["devicelab", "ios", "mac"]
|
["devicelab", "ios", "mac"]
|
||||||
task_name: flavors_test_ios
|
task_name: flavors_test_ios
|
||||||
|
|
||||||
|
- name: Mac_arm64_ios flavors_test_ios_xcode_debug
|
||||||
|
recipe: devicelab/devicelab_drone
|
||||||
|
presubmit: false
|
||||||
|
timeout: 60
|
||||||
|
properties:
|
||||||
|
tags: >
|
||||||
|
["devicelab", "ios", "mac"]
|
||||||
|
task_name: flavors_test_ios_xcode_debug
|
||||||
|
bringup: true
|
||||||
|
|
||||||
- name: Mac_ios flutter_gallery_ios__compile
|
- name: Mac_ios flutter_gallery_ios__compile
|
||||||
recipe: devicelab/devicelab_drone
|
recipe: devicelab/devicelab_drone
|
||||||
presubmit: false
|
presubmit: false
|
||||||
|
@ -174,6 +174,7 @@
|
|||||||
/dev/devicelab/bin/tasks/cubic_bezier_perf_ios_sksl_warmup__timeline_summary.dart @zanderso @flutter/engine
|
/dev/devicelab/bin/tasks/cubic_bezier_perf_ios_sksl_warmup__timeline_summary.dart @zanderso @flutter/engine
|
||||||
/dev/devicelab/bin/tasks/external_ui_integration_test_ios.dart @zanderso @flutter/engine
|
/dev/devicelab/bin/tasks/external_ui_integration_test_ios.dart @zanderso @flutter/engine
|
||||||
/dev/devicelab/bin/tasks/flavors_test_ios.dart @vashworth @flutter/tool
|
/dev/devicelab/bin/tasks/flavors_test_ios.dart @vashworth @flutter/tool
|
||||||
|
/dev/devicelab/bin/tasks/flavors_test_ios_xcode_debug.dart @vashworth @flutter/tool
|
||||||
/dev/devicelab/bin/tasks/flutter_gallery__transition_perf_e2e_ios.dart @zanderso @flutter/engine
|
/dev/devicelab/bin/tasks/flutter_gallery__transition_perf_e2e_ios.dart @zanderso @flutter/engine
|
||||||
/dev/devicelab/bin/tasks/flutter_gallery_ios__compile.dart @vashworth @flutter/engine
|
/dev/devicelab/bin/tasks/flutter_gallery_ios__compile.dart @vashworth @flutter/engine
|
||||||
/dev/devicelab/bin/tasks/flutter_gallery_ios__start_up.dart @vashworth @flutter/engine
|
/dev/devicelab/bin/tasks/flutter_gallery_ios__start_up.dart @vashworth @flutter/engine
|
||||||
|
52
dev/devicelab/bin/tasks/flavors_test_ios_xcode_debug.dart
Normal file
52
dev/devicelab/bin/tasks/flavors_test_ios_xcode_debug.dart
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// 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/framework/task_result.dart';
|
||||||
|
import 'package:flutter_devicelab/framework/utils.dart';
|
||||||
|
import 'package:flutter_devicelab/tasks/integration_tests.dart';
|
||||||
|
|
||||||
|
Future<void> main() async {
|
||||||
|
deviceOperatingSystem = DeviceOperatingSystem.ios;
|
||||||
|
await task(() async {
|
||||||
|
await createFlavorsTest(environment: <String, String>{
|
||||||
|
'FORCE_XCODE_DEBUG': 'true',
|
||||||
|
}).call();
|
||||||
|
await createIntegrationTestFlavorsTest(environment: <String, String>{
|
||||||
|
'FORCE_XCODE_DEBUG': 'true',
|
||||||
|
}).call();
|
||||||
|
// test install and uninstall of flavors app
|
||||||
|
final TaskResult installTestsResult = await inDirectory(
|
||||||
|
'${flutterDirectory.path}/dev/integration_tests/flavors',
|
||||||
|
() async {
|
||||||
|
await flutter(
|
||||||
|
'install',
|
||||||
|
options: <String>['--flavor', 'paid'],
|
||||||
|
);
|
||||||
|
await flutter(
|
||||||
|
'install',
|
||||||
|
options: <String>['--flavor', 'paid', '--uninstall-only'],
|
||||||
|
);
|
||||||
|
final StringBuffer stderr = StringBuffer();
|
||||||
|
await evalFlutter(
|
||||||
|
'install',
|
||||||
|
canFail: true,
|
||||||
|
stderr: stderr,
|
||||||
|
options: <String>['--flavor', 'bogus'],
|
||||||
|
);
|
||||||
|
|
||||||
|
final String stderrString = stderr.toString();
|
||||||
|
if (!stderrString.contains('The Xcode project defines schemes: free, paid')) {
|
||||||
|
print(stderrString);
|
||||||
|
return TaskResult.failure('Should not succeed with bogus flavor');
|
||||||
|
}
|
||||||
|
|
||||||
|
return TaskResult.success(null);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return installTestsResult;
|
||||||
|
});
|
||||||
|
}
|
@ -22,19 +22,21 @@ TaskFunction createPlatformInteractionTest() {
|
|||||||
).call;
|
).call;
|
||||||
}
|
}
|
||||||
|
|
||||||
TaskFunction createFlavorsTest() {
|
TaskFunction createFlavorsTest({Map<String, String>? environment}) {
|
||||||
return DriverTest(
|
return DriverTest(
|
||||||
'${flutterDirectory.path}/dev/integration_tests/flavors',
|
'${flutterDirectory.path}/dev/integration_tests/flavors',
|
||||||
'lib/main.dart',
|
'lib/main.dart',
|
||||||
extraOptions: <String>['--flavor', 'paid'],
|
extraOptions: <String>['--flavor', 'paid'],
|
||||||
|
environment: environment,
|
||||||
).call;
|
).call;
|
||||||
}
|
}
|
||||||
|
|
||||||
TaskFunction createIntegrationTestFlavorsTest() {
|
TaskFunction createIntegrationTestFlavorsTest({Map<String, String>? environment}) {
|
||||||
return IntegrationTest(
|
return IntegrationTest(
|
||||||
'${flutterDirectory.path}/dev/integration_tests/flavors',
|
'${flutterDirectory.path}/dev/integration_tests/flavors',
|
||||||
'integration_test/integration_test.dart',
|
'integration_test/integration_test.dart',
|
||||||
extraOptions: <String>['--flavor', 'paid'],
|
extraOptions: <String>['--flavor', 'paid'],
|
||||||
|
environment: environment,
|
||||||
).call;
|
).call;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,6 +221,7 @@ class IntegrationTest {
|
|||||||
this.extraOptions = const <String>[],
|
this.extraOptions = const <String>[],
|
||||||
this.createPlatforms = const <String>[],
|
this.createPlatforms = const <String>[],
|
||||||
this.withTalkBack = false,
|
this.withTalkBack = false,
|
||||||
|
this.environment,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -227,6 +230,7 @@ class IntegrationTest {
|
|||||||
final List<String> extraOptions;
|
final List<String> extraOptions;
|
||||||
final List<String> createPlatforms;
|
final List<String> createPlatforms;
|
||||||
final bool withTalkBack;
|
final bool withTalkBack;
|
||||||
|
final Map<String, String>? environment;
|
||||||
|
|
||||||
Future<TaskResult> call() {
|
Future<TaskResult> call() {
|
||||||
return inDirectory<TaskResult>(testDirectory, () async {
|
return inDirectory<TaskResult>(testDirectory, () async {
|
||||||
@ -258,7 +262,7 @@ class IntegrationTest {
|
|||||||
testTarget,
|
testTarget,
|
||||||
...extraOptions,
|
...extraOptions,
|
||||||
];
|
];
|
||||||
await flutter('test', options: options);
|
await flutter('test', options: options, environment: environment);
|
||||||
|
|
||||||
if (withTalkBack) {
|
if (withTalkBack) {
|
||||||
await disableTalkBack();
|
await disableTalkBack();
|
||||||
|
@ -877,6 +877,8 @@ class IOSDevice extends Device {
|
|||||||
projectInfo.reportFlavorNotFoundAndExit();
|
projectInfo.reportFlavorNotFoundAndExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_xcodeDebug.ensureXcodeDebuggerLaunchAction(project.xcodeProjectSchemeFile(scheme: scheme));
|
||||||
|
|
||||||
debugProject = XcodeDebugProject(
|
debugProject = XcodeDebugProject(
|
||||||
scheme: scheme,
|
scheme: scheme,
|
||||||
xcodeProject: project.xcodeProject,
|
xcodeProject: project.xcodeProject,
|
||||||
|
@ -6,7 +6,10 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:process/process.dart';
|
import 'package:process/process.dart';
|
||||||
|
import 'package:xml/xml.dart';
|
||||||
|
import 'package:xml/xpath.dart';
|
||||||
|
|
||||||
|
import '../base/common.dart';
|
||||||
import '../base/error_handling_io.dart';
|
import '../base/error_handling_io.dart';
|
||||||
import '../base/file_system.dart';
|
import '../base/file_system.dart';
|
||||||
import '../base/io.dart';
|
import '../base/io.dart';
|
||||||
@ -58,7 +61,6 @@ class XcodeDebug {
|
|||||||
required String deviceId,
|
required String deviceId,
|
||||||
required List<String> launchArguments,
|
required List<String> launchArguments,
|
||||||
}) async {
|
}) async {
|
||||||
|
|
||||||
// If project is not already opened in Xcode, open it.
|
// If project is not already opened in Xcode, open it.
|
||||||
if (!await _isProjectOpenInXcode(project: project)) {
|
if (!await _isProjectOpenInXcode(project: project)) {
|
||||||
final bool openResult = await _openProjectInXcode(xcodeWorkspace: project.xcodeWorkspace);
|
final bool openResult = await _openProjectInXcode(xcodeWorkspace: project.xcodeWorkspace);
|
||||||
@ -411,6 +413,49 @@ class XcodeDebug {
|
|||||||
verboseLogging: verboseLogging,
|
verboseLogging: verboseLogging,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ensure the Xcode project is set up to launch an LLDB debugger. If these
|
||||||
|
/// settings are not set, the launch will fail with a "Cannot create a
|
||||||
|
/// FlutterEngine instance in debug mode without Flutter tooling or Xcode."
|
||||||
|
/// error message. These settings should be correct by default, but some users
|
||||||
|
/// reported them not being so after upgrading to Xcode 15.
|
||||||
|
void ensureXcodeDebuggerLaunchAction(File schemeFile) {
|
||||||
|
if (!schemeFile.existsSync()) {
|
||||||
|
_logger.printError('Failed to find ${schemeFile.path}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String schemeXml = schemeFile.readAsStringSync();
|
||||||
|
try {
|
||||||
|
final XmlDocument document = XmlDocument.parse(schemeXml);
|
||||||
|
final Iterable<XmlNode> nodes = document.xpath('/Scheme/LaunchAction');
|
||||||
|
if (nodes.isEmpty) {
|
||||||
|
_logger.printError('Failed to find LaunchAction for the Scheme in ${schemeFile.path}.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final XmlNode launchAction = nodes.first;
|
||||||
|
final XmlAttribute? debuggerIdentifer = launchAction.attributes
|
||||||
|
.where((XmlAttribute attribute) =>
|
||||||
|
attribute.localName == 'selectedDebuggerIdentifier')
|
||||||
|
.firstOrNull;
|
||||||
|
final XmlAttribute? launcherIdentifer = launchAction.attributes
|
||||||
|
.where((XmlAttribute attribute) =>
|
||||||
|
attribute.localName == 'selectedLauncherIdentifier')
|
||||||
|
.firstOrNull;
|
||||||
|
if (debuggerIdentifer == null ||
|
||||||
|
launcherIdentifer == null ||
|
||||||
|
!debuggerIdentifer.value.contains('LLDB') ||
|
||||||
|
!launcherIdentifer.value.contains('LLDB')) {
|
||||||
|
throwToolExit('''
|
||||||
|
Your Xcode project is not setup to start a debugger. To fix this, launch Xcode
|
||||||
|
and select "Product > Scheme > Edit Scheme", select "Run" in the sidebar,
|
||||||
|
and ensure "Debug executable" is checked in the "Info" tab.
|
||||||
|
''');
|
||||||
|
}
|
||||||
|
} on XmlException catch (exception) {
|
||||||
|
_logger.printError('Failed to parse ${schemeFile.path}: $exception');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
|
@ -12,7 +12,7 @@ class XcodeProjectObjectVersionMigration extends ProjectMigrator {
|
|||||||
XcodeBasedProject project,
|
XcodeBasedProject project,
|
||||||
super.logger,
|
super.logger,
|
||||||
) : _xcodeProjectInfoFile = project.xcodeProjectInfoFile,
|
) : _xcodeProjectInfoFile = project.xcodeProjectInfoFile,
|
||||||
_xcodeProjectSchemeFile = project.xcodeProjectSchemeFile;
|
_xcodeProjectSchemeFile = project.xcodeProjectSchemeFile();
|
||||||
|
|
||||||
final File _xcodeProjectInfoFile;
|
final File _xcodeProjectInfoFile;
|
||||||
final File _xcodeProjectSchemeFile;
|
final File _xcodeProjectSchemeFile;
|
||||||
|
@ -68,8 +68,10 @@ abstract class XcodeBasedProject extends FlutterProjectPlatform {
|
|||||||
File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj');
|
File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj');
|
||||||
|
|
||||||
/// The 'Runner.xcscheme' file of [xcodeProject].
|
/// The 'Runner.xcscheme' file of [xcodeProject].
|
||||||
File get xcodeProjectSchemeFile =>
|
File xcodeProjectSchemeFile({String? scheme}) {
|
||||||
xcodeProject.childDirectory('xcshareddata').childDirectory('xcschemes').childFile('Runner.xcscheme');
|
final String schemeName = scheme ?? 'Runner';
|
||||||
|
return xcodeProject.childDirectory('xcshareddata').childDirectory('xcschemes').childFile('$schemeName.xcscheme');
|
||||||
|
}
|
||||||
|
|
||||||
File get xcodeProjectWorkspaceData =>
|
File get xcodeProjectWorkspaceData =>
|
||||||
xcodeProject
|
xcodeProject
|
||||||
|
@ -520,6 +520,81 @@ void main() {
|
|||||||
Xcode: () => xcode,
|
Xcode: () => xcode,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('with flavor', () {
|
||||||
|
setUp(() {
|
||||||
|
projectInfo = XcodeProjectInfo(
|
||||||
|
<String>['Runner'],
|
||||||
|
<String>['Debug', 'Release', 'Debug-free', 'Release-free'],
|
||||||
|
<String>['Runner', 'free'],
|
||||||
|
logger,
|
||||||
|
);
|
||||||
|
fakeXcodeProjectInterpreter = FakeXcodeProjectInterpreter(projectInfo: projectInfo);
|
||||||
|
xcode = Xcode.test(processManager: FakeProcessManager.any(), xcodeProjectInterpreter: fakeXcodeProjectInterpreter);
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('succeeds', () async {
|
||||||
|
final IOSDevice iosDevice = setUpIOSDevice(
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
processManager: FakeProcessManager.any(),
|
||||||
|
logger: logger,
|
||||||
|
artifacts: artifacts,
|
||||||
|
isCoreDevice: true,
|
||||||
|
coreDeviceControl: FakeIOSCoreDeviceControl(),
|
||||||
|
xcodeDebug: FakeXcodeDebug(
|
||||||
|
expectedProject: XcodeDebugProject(
|
||||||
|
scheme: 'free',
|
||||||
|
xcodeWorkspace: fileSystem.directory('/ios/Runner.xcworkspace'),
|
||||||
|
xcodeProject: fileSystem.directory('/ios/Runner.xcodeproj'),
|
||||||
|
hostAppProjectName: 'Runner',
|
||||||
|
),
|
||||||
|
expectedDeviceId: '123',
|
||||||
|
expectedLaunchArguments: <String>['--enable-dart-profiling'],
|
||||||
|
expectedSchemeFilePath: '/ios/Runner.xcodeproj/xcshareddata/xcschemes/free.xcscheme',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
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 LaunchResult launchResult = await iosDevice.startApp(
|
||||||
|
buildableIOSApp,
|
||||||
|
debuggingOptions: DebuggingOptions.enabled(const BuildInfo(
|
||||||
|
BuildMode.debug,
|
||||||
|
'free',
|
||||||
|
buildName: '1.2.3',
|
||||||
|
buildNumber: '4',
|
||||||
|
treeShakeIcons: false,
|
||||||
|
)),
|
||||||
|
platformArgs: <String, Object>{},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(logger.errorText, isEmpty);
|
||||||
|
expect(fileSystem.directory('build/ios/iphoneos'), exists);
|
||||||
|
expect(launchResult.started, true);
|
||||||
|
expect(processManager, hasNoRemainingExpectations);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
ProcessManager: () => FakeProcessManager.any(),
|
||||||
|
FileSystem: () => fileSystem,
|
||||||
|
Logger: () => logger,
|
||||||
|
Platform: () => macPlatform,
|
||||||
|
XcodeProjectInterpreter: () => fakeXcodeProjectInterpreter,
|
||||||
|
Xcode: () => xcode,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
testUsingContext('updates Generated.xcconfig before and after launch', () async {
|
testUsingContext('updates Generated.xcconfig before and after launch', () async {
|
||||||
final Completer<void> debugStartedCompleter = Completer<void>();
|
final Completer<void> debugStartedCompleter = Completer<void>();
|
||||||
final Completer<void> debugEndedCompleter = Completer<void>();
|
final Completer<void> debugEndedCompleter = Completer<void>();
|
||||||
@ -829,6 +904,7 @@ class FakeXcodeDebug extends Fake implements XcodeDebug {
|
|||||||
this.expectedProject,
|
this.expectedProject,
|
||||||
this.expectedDeviceId,
|
this.expectedDeviceId,
|
||||||
this.expectedLaunchArguments,
|
this.expectedLaunchArguments,
|
||||||
|
this.expectedSchemeFilePath = '/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme',
|
||||||
this.debugStartedCompleter,
|
this.debugStartedCompleter,
|
||||||
this.debugEndedCompleter,
|
this.debugEndedCompleter,
|
||||||
});
|
});
|
||||||
@ -840,6 +916,7 @@ class FakeXcodeDebug extends Fake implements XcodeDebug {
|
|||||||
final List<String>? expectedLaunchArguments;
|
final List<String>? expectedLaunchArguments;
|
||||||
final Completer<void>? debugStartedCompleter;
|
final Completer<void>? debugStartedCompleter;
|
||||||
final Completer<void>? debugEndedCompleter;
|
final Completer<void>? debugEndedCompleter;
|
||||||
|
final String expectedSchemeFilePath;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> debugApp({
|
Future<bool> debugApp({
|
||||||
@ -863,6 +940,11 @@ class FakeXcodeDebug extends Fake implements XcodeDebug {
|
|||||||
await debugEndedCompleter?.future;
|
await debugEndedCompleter?.future;
|
||||||
return debugSuccess;
|
return debugSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void ensureXcodeDebuggerLaunchAction(File schemeFile) {
|
||||||
|
expect(schemeFile.path, expectedSchemeFilePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FakeIOSCoreDeviceControl extends Fake implements IOSCoreDeviceControl {
|
class FakeIOSCoreDeviceControl extends Fake implements IOSCoreDeviceControl {
|
||||||
|
@ -662,7 +662,7 @@ platform :ios, '11.0'
|
|||||||
project.xcodeProjectInfoFile = xcodeProjectInfoFile;
|
project.xcodeProjectInfoFile = xcodeProjectInfoFile;
|
||||||
|
|
||||||
xcodeProjectSchemeFile = memoryFileSystem.file('Runner.xcscheme');
|
xcodeProjectSchemeFile = memoryFileSystem.file('Runner.xcscheme');
|
||||||
project.xcodeProjectSchemeFile = xcodeProjectSchemeFile;
|
project.schemeFile = xcodeProjectSchemeFile;
|
||||||
});
|
});
|
||||||
|
|
||||||
testWithoutContext('skipped if files are missing', () {
|
testWithoutContext('skipped if files are missing', () {
|
||||||
@ -1370,8 +1370,10 @@ class FakeIosProject extends Fake implements IosProject {
|
|||||||
@override
|
@override
|
||||||
File xcodeProjectInfoFile = MemoryFileSystem.test().file('xcodeProjectInfoFile');
|
File xcodeProjectInfoFile = MemoryFileSystem.test().file('xcodeProjectInfoFile');
|
||||||
|
|
||||||
|
File? schemeFile;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
File xcodeProjectSchemeFile = MemoryFileSystem.test().file('xcodeProjectSchemeFile');
|
File xcodeProjectSchemeFile({String? scheme}) => schemeFile ?? MemoryFileSystem.test().file('xcodeProjectSchemeFile');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
File appFrameworkInfoPlist = MemoryFileSystem.test().file('appFrameworkInfoPlist');
|
File appFrameworkInfoPlist = MemoryFileSystem.test().file('appFrameworkInfoPlist');
|
||||||
|
@ -1064,6 +1064,96 @@ void main() {
|
|||||||
expect(status, isFalse);
|
expect(status, isFalse);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('ensureXcodeDebuggerLaunchAction', () {
|
||||||
|
late Xcode xcode;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
xcode = setupXcode(
|
||||||
|
fakeProcessManager: fakeProcessManager,
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
flutterRoot: flutterRoot,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('succeeds', () async {
|
||||||
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
||||||
|
logger: logger,
|
||||||
|
processManager: fakeProcessManager,
|
||||||
|
xcode: xcode,
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
);
|
||||||
|
|
||||||
|
final File schemeFile = fileSystem.file('ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme');
|
||||||
|
schemeFile.createSync(recursive: true);
|
||||||
|
schemeFile.writeAsStringSync(validSchemeXml);
|
||||||
|
|
||||||
|
xcodeDebug.ensureXcodeDebuggerLaunchAction(schemeFile);
|
||||||
|
expect(logger.errorText, isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('prints error if scheme file not found', () async {
|
||||||
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
||||||
|
logger: logger,
|
||||||
|
processManager: fakeProcessManager,
|
||||||
|
xcode: xcode,
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
);
|
||||||
|
|
||||||
|
final File schemeFile = fileSystem.file('ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme');
|
||||||
|
|
||||||
|
xcodeDebug.ensureXcodeDebuggerLaunchAction(schemeFile);
|
||||||
|
expect(logger.errorText.contains('Failed to find'), isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('throws error if launch action is missing debugger info', () async {
|
||||||
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
||||||
|
logger: logger,
|
||||||
|
processManager: fakeProcessManager,
|
||||||
|
xcode: xcode,
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
);
|
||||||
|
|
||||||
|
final File schemeFile = fileSystem.file('ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme');
|
||||||
|
schemeFile.createSync(recursive: true);
|
||||||
|
schemeFile.writeAsStringSync(disabledDebugExecutableSchemeXml);
|
||||||
|
|
||||||
|
expect(() => xcodeDebug.ensureXcodeDebuggerLaunchAction(schemeFile),
|
||||||
|
throwsToolExit(message: 'Your Xcode project is not setup to start a debugger.'));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('prints error if unable to find launch action', () async {
|
||||||
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
||||||
|
logger: logger,
|
||||||
|
processManager: fakeProcessManager,
|
||||||
|
xcode: xcode,
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
);
|
||||||
|
|
||||||
|
final File schemeFile = fileSystem.file('ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme');
|
||||||
|
schemeFile.createSync(recursive: true);
|
||||||
|
schemeFile.writeAsStringSync('<?xml version="1.0" encoding="UTF-8"?><Scheme></Scheme>');
|
||||||
|
|
||||||
|
xcodeDebug.ensureXcodeDebuggerLaunchAction(schemeFile);
|
||||||
|
expect(logger.errorText.contains('Failed to find LaunchAction for the Scheme'), isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('prints error if invalid xml', () async {
|
||||||
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
||||||
|
logger: logger,
|
||||||
|
processManager: fakeProcessManager,
|
||||||
|
xcode: xcode,
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
);
|
||||||
|
|
||||||
|
final File schemeFile = fileSystem.file('ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme');
|
||||||
|
schemeFile.createSync(recursive: true);
|
||||||
|
schemeFile.writeAsStringSync('<?xml version="1.0" encoding="UTF-8"?><Scheme>');
|
||||||
|
|
||||||
|
xcodeDebug.ensureXcodeDebuggerLaunchAction(schemeFile);
|
||||||
|
expect(logger.errorText.contains('Failed to parse'), isTrue);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('Debug project through Xcode with app bundle', () {
|
group('Debug project through Xcode with app bundle', () {
|
||||||
@ -1161,3 +1251,89 @@ class FakeProcess extends Fake implements Process {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const String validSchemeXml = '''
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1430"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Profile"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
|
''';
|
||||||
|
|
||||||
|
const String disabledDebugExecutableSchemeXml = '''
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1430"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = ""
|
||||||
|
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Profile"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
|
''';
|
||||||
|
Loading…
Reference in New Issue
Block a user