Skip to content

Commit d49d805

Browse files
authored
Add LLDB warnings (#170827)
LLDB is now required again for iOS 26+ so add back warnings. ## 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 97853d5 commit d49d805

File tree

8 files changed

+370
-79
lines changed

8 files changed

+370
-79
lines changed

dev/devicelab/bin/tasks/build_ios_framework_module_test.dart

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

454-
if (File(path.join(outputPath, 'flutter_lldbinit')).existsSync() == isModule) {
455-
throw TaskResult.failure('Unexpected flutter_lldbinit');
456-
}
457-
458-
if (File(path.join(outputPath, 'flutter_lldb_helper.py')).existsSync() == isModule) {
459-
throw TaskResult.failure('Unexpected flutter_lldb_helper.py.');
460-
}
461-
462454
section('Build frameworks without plugins');
463455
await _testBuildFrameworksWithoutPlugins(projectDir, platform: 'ios');
464456

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

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:meta/meta.dart';
77
import '../../artifacts.dart';
88
import '../../base/io.dart';
99
import '../../build_info.dart';
10+
import '../../globals.dart' as globals show stdio;
1011
import '../build_system.dart';
1112

1213
abstract class UnpackDarwin extends Target {
@@ -111,3 +112,63 @@ abstract class UnpackDarwin extends Target {
111112
}
112113
}
113114
}
115+
116+
/// Log warning message to the Xcode build logs. Log will show as yellow with an icon.
117+
///
118+
/// If the issue occurs in a specific file, include the [filePath] as an absolute path.
119+
/// If the issue occurs at a specific line in the file, include the [lineNumber] as well.
120+
/// The [filePath] and [lineNumber] are optional.
121+
void printXcodeWarning(String warning, {String? filePath, int? lineNumber}) {
122+
_printXcodeLog(XcodeLogType.warning, warning, filePath: filePath, lineNumber: lineNumber);
123+
}
124+
125+
/// Log error message to the Xcode build logs. Log will show as red with an icon and may cause the build to fail.
126+
///
127+
/// If the issue occurs in a specific file, include the [filePath] as an absolute path.
128+
/// If the issue occurs at a specific line in the file, include the [lineNumber] as well.
129+
/// The [filePath] and [lineNumber] are optional.
130+
void printXcodeError(String error, {String? filePath, int? lineNumber}) {
131+
_printXcodeLog(XcodeLogType.error, error, filePath: filePath, lineNumber: lineNumber);
132+
}
133+
134+
/// Log note message to the Xcode build logs. Log will show with no special color or icon.
135+
///
136+
/// If the issue occurs in a specific file, include the [filePath] as an absolute path.
137+
/// If the issue occurs at a specific line in the file, include the [lineNumber] as well.
138+
/// The [filePath] and [lineNumber] are optional.
139+
void printXcodeNote(String note, {String? filePath, int? lineNumber}) {
140+
_printXcodeLog(XcodeLogType.note, note, filePath: filePath, lineNumber: lineNumber);
141+
}
142+
143+
/// Log [message] to the Xcode build logs.
144+
///
145+
/// If [logType] is [XcodeLogType.error], log will show as red with an icon and may cause the build to fail.
146+
/// If [logType] is [XcodeLogType.warning], log will show as yellow with an icon.
147+
/// If [logType] is [XcodeLogType.note], log will show with no special color or icon.
148+
///
149+
///
150+
/// If the issue occurs in a specific file, include the [filePath] as an absolute path.
151+
/// If the issue occurs at a specific line in the file, include the [lineNumber] as well.
152+
/// The [filePath] and [lineNumber] are optional.
153+
///
154+
/// See Apple's documentation:
155+
/// https://developer.apple.com/documentation/xcode/running-custom-scripts-during-a-build#Log-errors-and-warnings-from-your-script
156+
void _printXcodeLog(XcodeLogType logType, String message, {String? filePath, int? lineNumber}) {
157+
String linePath = '';
158+
if (filePath != null) {
159+
linePath = '$filePath:';
160+
161+
// A line number is meaningless without a filePath, so only set if filePath is also provided.
162+
if (lineNumber != null) {
163+
linePath = '$linePath$lineNumber:';
164+
}
165+
}
166+
if (linePath.isNotEmpty) {
167+
linePath = '$linePath ';
168+
}
169+
170+
// Must be printed to stderr to be streamed to the Flutter tool in xcode_backend.dart.
171+
globals.stdio.stderrWrite('$linePath${logType.name}: $message\n');
172+
}
173+
174+
enum XcodeLogType { error, warning, note }

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

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import '../../base/file_system.dart';
1212
import '../../base/io.dart';
1313
import '../../base/logger.dart' show Logger;
1414
import '../../base/process.dart';
15+
import '../../base/version.dart';
1516
import '../../build_info.dart';
1617
import '../../darwin/darwin.dart';
1718
import '../../devfs.dart';
@@ -486,6 +487,113 @@ class _IssueLaunchRootViewControllerAccess extends Target {
486487
List<Source> get outputs => <Source>[];
487488
}
488489

490+
/// This target verifies that the Xcode project has an LLDB Init File set within
491+
/// at least one scheme.
492+
///
493+
/// LLDB Init File is needed for debugging on physical iOS 26+ devices.
494+
class DebugIosLLDBInit extends Target {
495+
const DebugIosLLDBInit();
496+
497+
@override
498+
String get name => 'debug_ios_lldb_init';
499+
500+
@override
501+
List<Source> get inputs => const <Source>[
502+
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/ios.dart'),
503+
Source.pattern(
504+
'{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/darwin.dart',
505+
),
506+
];
507+
508+
@override
509+
List<Source> get outputs => <Source>[
510+
Source.fromProject((FlutterProject project) => project.ios.lldbInitFile),
511+
];
512+
513+
@override
514+
List<Target> get dependencies => <Target>[];
515+
516+
@override
517+
Future<void> build(Environment environment) async {
518+
final String? sdkRoot = environment.defines[kSdkRoot];
519+
if (sdkRoot == null) {
520+
throw MissingDefineException(kSdkRoot, name);
521+
}
522+
final EnvironmentType? environmentType = environmentTypeFromSdkroot(
523+
sdkRoot,
524+
environment.fileSystem,
525+
);
526+
527+
// LLDB Init File is only required for physical devices in debug mode.
528+
if (environmentType != EnvironmentType.physical) {
529+
return;
530+
}
531+
532+
final String? targetDeviceVersionString = environment.defines[kTargetDeviceOSVersion];
533+
if (targetDeviceVersionString == null) {
534+
// Skip if TARGET_DEVICE_OS_VERSION is not found. TARGET_DEVICE_OS_VERSION
535+
// is not set if "build ios-framework" is called, which builds the
536+
// DebugIosApplicationBundle directly rather than through flutter assemble.
537+
// If may also be null if the build is targeting multiple architectures.
538+
return;
539+
}
540+
541+
final Version? targetDeviceVersion = Version.parse(targetDeviceVersionString);
542+
if (targetDeviceVersion == null) {
543+
environment.logger.printError(
544+
'Failed to parse TARGET_DEVICE_OS_VERSION: $targetDeviceVersionString',
545+
);
546+
return;
547+
}
548+
549+
// LLDB Init File is only needed for iOS 26+.
550+
if (targetDeviceVersion < Version(26, 0, null)) {
551+
return;
552+
}
553+
554+
final String? srcRoot = environment.defines[kSrcRoot];
555+
if (srcRoot == null) {
556+
environment.logger.printError('Failed to find $srcRoot');
557+
return;
558+
}
559+
560+
final Directory xcodeProjectDir = environment.fileSystem.directory(srcRoot);
561+
if (!xcodeProjectDir.existsSync()) {
562+
environment.logger.printError('Failed to find ${xcodeProjectDir.path}');
563+
return;
564+
}
565+
566+
// The scheme name is not available in Xcode Build Phases Run Scripts.
567+
// Instead, find all xcscheme files in the Xcode project (this may be the
568+
// Flutter Xcode project or an Add to App native Xcode project) and check
569+
// if any of them contain "customLLDBInitFile". If none have it set, print
570+
// a warning.
571+
// Also, this cannot check for a specific path/name for the LLDB Init File
572+
// since Flutter's LLDB Init file may be imported from within a user's
573+
// custom LLDB Init File.
574+
bool anyLLDBInitFound = false;
575+
await for (final FileSystemEntity entity in xcodeProjectDir.list(recursive: true)) {
576+
if (environment.fileSystem.path.extension(entity.path) == '.xcscheme' && entity is File) {
577+
if (entity.readAsStringSync().contains('customLLDBInitFile')) {
578+
anyLLDBInitFound = true;
579+
break;
580+
}
581+
}
582+
}
583+
if (!anyLLDBInitFound) {
584+
final FlutterProject flutterProject = FlutterProject.fromDirectory(environment.projectDir);
585+
final String tab = flutterProject.isModule ? 'Use CocoaPods' : 'Use frameworks';
586+
printXcodeWarning(
587+
'Debugging Flutter on new iOS versions requires an LLDB Init File. To '
588+
'ensure debug mode works, please complete instructions found in '
589+
'"Embed a Flutter module in your iOS app > $tab > Set LLDB Init File" '
590+
'section of https://docs.flutter.dev/to/ios-add-to-app-embed-setup.',
591+
);
592+
}
593+
return;
594+
}
595+
}
596+
489597
/// The base class for all iOS bundle targets.
490598
///
491599
/// This is responsible for setting up the basic App.framework structure, including:
@@ -641,7 +749,11 @@ class DebugIosApplicationBundle extends IosAssetBundle {
641749
];
642750

643751
@override
644-
List<Target> get dependencies => <Target>[const DebugUniversalFramework(), ...super.dependencies];
752+
List<Target> get dependencies => <Target>[
753+
const DebugUniversalFramework(),
754+
const DebugIosLLDBInit(),
755+
...super.dependencies,
756+
];
645757
}
646758

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

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,12 +374,22 @@ class BuildIOSFrameworkCommand extends BuildFrameworkCommand {
374374
);
375375
}
376376

377-
if (!project.isModule && buildInfos.any((BuildInfo info) => info.isDebug)) {
377+
if (buildInfos.any((BuildInfo info) => info.isDebug)) {
378378
// Add-to-App must manually add the LLDB Init File to their native Xcode
379379
// project, so provide the files and instructions.
380380
final File lldbInitSourceFile = project.ios.lldbInitFile;
381381
final File lldbInitTargetFile = outputDirectory.childFile(lldbInitSourceFile.basename);
382382
final File lldbHelperPythonFile = project.ios.lldbHelperPythonFile;
383+
384+
if (!lldbInitTargetFile.existsSync()) {
385+
// If LLDB is being added to the output, print a warning with instructions on how to add.
386+
globals.printWarning(
387+
'Debugging Flutter on new iOS versions requires an LLDB Init File. To '
388+
'ensure debug mode works, please complete instructions found in '
389+
'"Embed a Flutter module in your iOS app > Use frameworks > Set LLDB Init File" '
390+
'section of https://docs.flutter.dev/to/ios-add-to-app-embed-setup.',
391+
);
392+
}
383393
lldbInitSourceFile.copySync(lldbInitTargetFile.path);
384394
lldbHelperPythonFile.copySync(outputDirectory.childFile(lldbHelperPythonFile.basename).path);
385395
}

packages/flutter_tools/lib/src/xcode_project.dart

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -774,20 +774,6 @@ def __lldb_init_module(debugger: lldb.SBDebugger, _):
774774
Future<void> _updateLLDBIfNeeded() async {
775775
if (globals.cache.isOlderThanToolsStamp(lldbInitFile) ||
776776
globals.cache.isOlderThanToolsStamp(lldbHelperPythonFile)) {
777-
if (isModule) {
778-
// When building a module project for Add-to-App, provide instructions
779-
// to manually add the LLDB Init File to their native Xcode project.
780-
globals.logger.printWarning(
781-
'Debugging Flutter on new iOS versions requires an LLDB Init File. '
782-
'To ensure debug mode works, please complete one of the following in '
783-
'your native Xcode project:\n'
784-
' * Open Xcode > Product > Scheme > Edit Scheme. For both the Run and Test actions, set LLDB Init File to: \n\n'
785-
' ${lldbInitFile.path}\n\n'
786-
' * If you are already using an LLDB Init File, please append the '
787-
'following to your LLDB Init File:\n\n'
788-
' command source ${lldbInitFile.path}\n',
789-
);
790-
}
791777
await _renderTemplateToFile(_lldbInitTemplate, null, lldbInitFile, globals.templateRenderer);
792778
await _renderTemplateToFile(
793779
_lldbPythonHelperTemplate,

0 commit comments

Comments
 (0)