Skip to content

Commit eb07c51

Browse files
authored
Add lldb init file (#164344)
Adds an .lldbinit file to iOS app xcscheme. Adding to scheme files can be error prone since a developer may be using custom schemes (flavors). If we can't add it to the scheme, we print an error without failing. Since it is part of the scheme, it will be added to the project and will be used on every run regardless of the device type/version. The Dart side handles limiting to specific devices. If needed, we can alter the .lldbinit file during `flutter assemble` to rewrite it since it doesn't read the file until launch time (therefore it can be changed during build time). During `flutter assemble`, if the project doesn't have an LLDB Init File set for any schemes, it'll throw an error if running in debug mode with an iOS 18.4+ device. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
1 parent 8876bcc commit eb07c51

File tree

19 files changed

+1935
-8
lines changed

19 files changed

+1935
-8
lines changed

.ci.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5251,6 +5251,22 @@ targets:
52515251
["devicelab", "ios", "mac"]
52525252
task_name: hot_mode_dev_cycle_ios__benchmark
52535253

5254+
- name: Mac_arm64_ios hot_mode_dev_cycle_ios_beta__benchmark
5255+
recipe: devicelab/devicelab_drone
5256+
presubmit: false
5257+
bringup: true
5258+
timeout: 60
5259+
properties:
5260+
os: Mac-15
5261+
device_os: iOS-18.4
5262+
$flutter/osx_sdk : >-
5263+
{
5264+
"sdk_version": "16e5104o"
5265+
}
5266+
tags: >
5267+
["devicelab", "ios", "mac"]
5268+
task_name: hot_mode_dev_cycle_ios__benchmark
5269+
52545270
- name: Mac_arm64_ios hot_mode_dev_cycle_ios__benchmark
52555271
recipe: devicelab/devicelab_drone
52565272
presubmit: false

dev/devicelab/bin/tasks/build_ios_framework_module_test.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,14 @@ Future<void> _testBuildIosFramework(Directory projectDir, {bool isModule = false
456456
throw TaskResult.failure('Unexpected GeneratedPluginRegistrant.m.');
457457
}
458458

459+
if (File(path.join(outputPath, 'flutter_lldbinit')).existsSync() == isModule) {
460+
throw TaskResult.failure('Unexpected flutter_lldbinit');
461+
}
462+
463+
if (File(path.join(outputPath, 'flutter_lldb_helper.py')).existsSync() == isModule) {
464+
throw TaskResult.failure('Unexpected flutter_lldb_helper.py.');
465+
}
466+
459467
section('Build frameworks without plugins');
460468
await _testBuildFrameworksWithoutPlugins(projectDir, platform: 'ios');
461469

packages/flutter_tools/bin/xcode_backend.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,8 @@ class Context {
454454
'--ExtraGenSnapshotOptions=${environment['EXTRA_GEN_SNAPSHOT_OPTIONS'] ?? ''}',
455455
'--DartDefines=${environment['DART_DEFINES'] ?? ''}',
456456
'--ExtraFrontEndOptions=${environment['EXTRA_FRONT_END_OPTIONS'] ?? ''}',
457+
'-dSrcRoot=${environment['SRCROOT'] ?? ''}',
458+
'-dTargetDeviceOSVersion=${environment['TARGET_DEVICE_OS_VERSION'] ?? ''}',
457459
]);
458460

459461
if (command == 'prepare') {

packages/flutter_tools/lib/src/build_info.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,15 @@ const String kAppFlavor = 'FLUTTER_APP_FLAVOR';
973973
/// The Xcode configuration used to build the project.
974974
const String kXcodeConfiguration = 'Configuration';
975975

976+
/// The Xcode build setting SRCROOT. Identifies the directory containing the
977+
/// Xcode target's source files.
978+
const String kSrcRoot = 'SrcRoot';
979+
980+
/// The Xcode build setting TARGET_DEVICE_OS_VERSION. The iOS version of the
981+
/// target device. Only available if a specific device is being targeted during
982+
/// the build.
983+
const String kTargetDeviceOSVersion = 'TargetDeviceOSVersion';
984+
976985
/// The define to pass build number
977986
const String kBuildNumber = 'BuildNumber';
978987

packages/flutter_tools/lib/src/build_system/targets/ios.dart

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import '../../base/common.dart';
1111
import '../../base/file_system.dart';
1212
import '../../base/io.dart';
1313
import '../../base/process.dart';
14+
import '../../base/version.dart';
1415
import '../../build_info.dart';
1516
import '../../devfs.dart';
1617
import '../../globals.dart' as globals;
@@ -432,6 +433,122 @@ class DebugUnpackIOS extends UnpackIOS {
432433
BuildMode get buildMode => BuildMode.debug;
433434
}
434435

436+
abstract class IosLLDBInit extends Target {
437+
const IosLLDBInit();
438+
439+
@override
440+
List<Source> get inputs => <Source>[
441+
const Source.pattern(
442+
'{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/ios.dart',
443+
),
444+
];
445+
446+
@override
447+
List<Source> get outputs {
448+
final FlutterProject flutterProject = FlutterProject.current();
449+
final String lldbInitFilePath = flutterProject.ios.lldbInitFile.path.replaceFirst(
450+
flutterProject.directory.path,
451+
'{PROJECT_DIR}/',
452+
);
453+
return <Source>[Source.pattern(lldbInitFilePath)];
454+
}
455+
456+
@override
457+
List<Target> get dependencies => <Target>[];
458+
459+
@visibleForOverriding
460+
BuildMode get buildMode;
461+
462+
@override
463+
Future<void> build(Environment environment) async {
464+
final String? sdkRoot = environment.defines[kSdkRoot];
465+
if (sdkRoot == null) {
466+
throw MissingDefineException(kSdkRoot, name);
467+
}
468+
final EnvironmentType? environmentType = environmentTypeFromSdkroot(
469+
sdkRoot,
470+
environment.fileSystem,
471+
);
472+
473+
// LLDB Init File is only required for physical devices in debug mode.
474+
if (!buildMode.isJit || environmentType != EnvironmentType.physical) {
475+
return;
476+
}
477+
478+
final String? targetDeviceVersionString = environment.defines[kTargetDeviceOSVersion];
479+
if (targetDeviceVersionString == null) {
480+
// Skip if TARGET_DEVICE_OS_VERSION is not found. TARGET_DEVICE_OS_VERSION
481+
// is not set if "build ios-framework" is called, which builds the
482+
// DebugIosApplicationBundle directly rather than through flutter assemble.
483+
// If may also be null if the build is targeting multiple device types.
484+
return;
485+
}
486+
final Version? targetDeviceVersion = Version.parse(targetDeviceVersionString);
487+
if (targetDeviceVersion == null) {
488+
environment.logger.printError(
489+
'Failed to parse Target Device Version $targetDeviceVersionString',
490+
);
491+
return;
492+
}
493+
494+
// LLDB Init File is only needed for iOS 18.4+.
495+
if (targetDeviceVersion < Version(18, 4, null)) {
496+
return;
497+
}
498+
499+
// The scheme name is not available in Xcode Build Phases Run Scripts.
500+
// Instead, find all xcscheme files in the Xcode project (this may be the
501+
// Flutter Xcode project or an Add to App native Xcode project) and check
502+
// if any of them contain "customLLDBInitFile". If none have it set, throw
503+
// an error.
504+
final String? srcRoot = environment.defines[kSrcRoot];
505+
if (srcRoot == null) {
506+
throw MissingDefineException(kSdkRoot, name);
507+
}
508+
final Directory xcodeProjectDir = environment.fileSystem.directory(srcRoot);
509+
if (!xcodeProjectDir.existsSync()) {
510+
throw Exception('Failed to find ${xcodeProjectDir.path}');
511+
}
512+
513+
bool anyLLDBInitFound = false;
514+
await for (final FileSystemEntity entity in xcodeProjectDir.list(recursive: true)) {
515+
if (environment.fileSystem.path.extension(entity.path) == '.xcscheme' && entity is File) {
516+
if (entity.readAsStringSync().contains('customLLDBInitFile')) {
517+
anyLLDBInitFound = true;
518+
break;
519+
}
520+
}
521+
}
522+
if (!anyLLDBInitFound) {
523+
final FlutterProject flutterProject = FlutterProject.fromDirectory(environment.projectDir);
524+
if (flutterProject.isModule) {
525+
throwToolExit(
526+
'Debugging Flutter on new iOS versions requires an LLDB Init File. To '
527+
'ensure debug mode works, please run "flutter build ios --config-only" '
528+
'in your Flutter project and follow the instructions to add the file.',
529+
);
530+
} else {
531+
throwToolExit(
532+
'Debugging Flutter on new iOS versions requires an LLDB Init File. To '
533+
'ensure debug mode works, please run "flutter build ios --config-only" '
534+
'in your Flutter project and automatically add the files.',
535+
);
536+
}
537+
}
538+
return;
539+
}
540+
}
541+
542+
class DebugIosLLDBInit extends IosLLDBInit {
543+
const DebugIosLLDBInit();
544+
545+
@override
546+
String get name => 'debug_ios_lldb_init';
547+
548+
@override
549+
BuildMode get buildMode => BuildMode.debug;
550+
}
551+
435552
/// The base class for all iOS bundle targets.
436553
///
437554
/// This is responsible for setting up the basic App.framework structure, including:
@@ -583,7 +700,11 @@ class DebugIosApplicationBundle extends IosAssetBundle {
583700
];
584701

585702
@override
586-
List<Target> get dependencies => <Target>[const DebugUniversalFramework(), ...super.dependencies];
703+
List<Target> get dependencies => <Target>[
704+
const DebugUniversalFramework(),
705+
const DebugIosLLDBInit(),
706+
...super.dependencies,
707+
];
587708
}
588709

589710
/// IosAssetBundle with debug symbols, used for Profile and Release builds.

packages/flutter_tools/lib/src/commands/build_ios_framework.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,27 @@ class BuildIOSFrameworkCommand extends BuildFrameworkCommand {
372372
);
373373
}
374374

375+
if (!project.isModule && buildInfos.any((BuildInfo info) => info.isDebug)) {
376+
// Add-to-App must manually add the LLDB Init File to their native Xcode
377+
// project, so provide the files and instructions.
378+
final File lldbInitSourceFile = project.ios.lldbInitFile;
379+
final File lldbInitTargetFile = outputDirectory.childFile(lldbInitSourceFile.basename);
380+
final File lldbHelperPythonFile = project.ios.lldbHelperPythonFile;
381+
lldbInitSourceFile.copySync(lldbInitTargetFile.path);
382+
lldbHelperPythonFile.copySync(outputDirectory.childFile(lldbHelperPythonFile.basename).path);
383+
globals.printStatus(
384+
'\nDebugging Flutter on new iOS versions requires an LLDB Init File. To '
385+
'ensure debug mode works, please complete one of the following in your '
386+
'native Xcode project:\n'
387+
' * Open Xcode > Product > Scheme > Edit Scheme. For both the Run and '
388+
'Test actions, set LLDB Init File to: \n\n'
389+
' ${lldbInitTargetFile.path}\n\n'
390+
' * If you are already using an LLDB Init File, please append the '
391+
'following to your LLDB Init File:\n\n'
392+
' command source ${lldbInitTargetFile.path}\n',
393+
);
394+
}
395+
375396
return FlutterCommandResult.success();
376397
}
377398

packages/flutter_tools/lib/src/ios/mac.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import '../globals.dart' as globals;
2525
import '../macos/cocoapod_utils.dart';
2626
import '../macos/swift_package_manager.dart';
2727
import '../macos/xcode.dart';
28+
import '../migrations/lldb_init_migration.dart';
2829
import '../migrations/swift_package_manager_gitignore_migration.dart';
2930
import '../migrations/swift_package_manager_integration_migration.dart';
3031
import '../migrations/xcode_project_object_version_migration.dart';
@@ -168,6 +169,14 @@ Future<XcodeBuildResult> buildXcodeProject({
168169
),
169170
SwiftPackageManagerGitignoreMigration(project, globals.logger),
170171
MetalAPIValidationMigrator.ios(app.project, globals.logger),
172+
LLDBInitMigration(
173+
app.project,
174+
buildInfo,
175+
globals.logger,
176+
deviceID: deviceID,
177+
fileSystem: globals.fs,
178+
environmentType: environmentType,
179+
),
171180
];
172181

173182
final ProjectMigration migration = ProjectMigration(migrators);

0 commit comments

Comments
 (0)