Pierre f234d26926
Clean up links to docs website (#177792)
- remove link without use (PowerShell version minimum not reached, this
is not mentionned anywhere in Windows installation / troubleshooting
documentation)
- clean up API docs root
- clean up app template links, add `Learn Flutter` link
- update get started links
- replace `flutter.dev/docs` with `docs.flutter.dev`
- fix embedder descriptions
- fix broken API `docs.flutter.io` links
- http → https 
- remove `/install` from `/get-started` links
- fix Android Studio link

## 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].

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- 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

---------

Co-authored-by: Parker Lougheed <parlough@gmail.com>
2025-11-01 18:38:27 +00:00

332 lines
11 KiB
Dart

// 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 'dart:async';
import 'package:args/command_runner.dart';
import 'package:intl/intl.dart' as intl;
import 'package:intl/intl_standalone.dart' as intl_standalone;
import 'package:process/process.dart';
import 'package:unified_analytics/unified_analytics.dart';
import 'src/base/async_guard.dart';
import 'src/base/common.dart';
import 'src/base/context.dart';
import 'src/base/error_handling_io.dart';
import 'src/base/exit.dart';
import 'src/base/file_system.dart';
import 'src/base/io.dart';
import 'src/base/logger.dart';
import 'src/base/process.dart';
import 'src/context_runner.dart';
import 'src/doctor.dart';
import 'src/features.dart';
import 'src/globals.dart' as globals;
import 'src/reporting/crash_reporting.dart';
import 'src/runner/flutter_command.dart';
import 'src/runner/flutter_command_runner.dart';
/// Runs the Flutter tool with support for the specified list of [commands].
Future<int> run(
List<String> args,
List<FlutterCommand> Function() commands, {
bool muteCommandLogging = false,
bool verbose = false,
bool verboseHelp = false,
bool? reportCrashes,
String? flutterVersion,
Map<Type, Generator>? overrides,
required ShutdownHooks shutdownHooks,
}) async {
if (muteCommandLogging) {
// Remove the verbose option; for help and doctor, users don't need to see
// verbose logs.
args = List<String>.of(args);
args.removeWhere((String option) => option == '-vv' || option == '-v' || option == '--verbose');
}
final bool usingLocalEngine = args.any((a) => a.startsWith('--local-engine'));
return runInContext<int>(() async {
globals.terminal.applyFeatureFlags(featureFlags);
reportCrashes ??= !await globals.isRunningOnBot;
final runner = FlutterCommandRunner(verboseHelp: verboseHelp);
commands().forEach(runner.addCommand);
// Initialize the system locale.
final String systemLocale = await intl_standalone.findSystemLocale();
intl.Intl.defaultLocale = intl.Intl.verifiedLocale(
systemLocale,
intl.NumberFormat.localeExists,
onFailure: (String _) => 'en_US',
);
String getVersion() =>
flutterVersion ?? globals.flutterVersion.getVersionString(redactUnknownBranches: true);
Object? firstError;
StackTrace? firstStackTrace;
return runZonedGuarded<Future<int>>(
() async {
try {
if (args.contains('--disable-analytics') && args.contains('--enable-analytics')) {
throwToolExit(
'Both enable and disable analytics commands were detected '
'when only one can be supplied per invocation.',
exitCode: 1,
);
}
if (args.contains('--disable-analytics')) {
if (globals.analytics.telemetryEnabled) {
globals.analytics.send(Event.analyticsCollectionEnabled(status: false));
// Before disablig analytics, we need to close the client to make
// sure the above collection event is sent.
await globals.analytics.close();
}
await globals.analytics.setTelemetry(false);
globals.printStatus('Analytics reporting disabled.');
}
if (args.contains('--enable-analytics')) {
final bool alreadyEnabled = globals.analytics.telemetryEnabled;
await globals.analytics.setTelemetry(true);
if (!alreadyEnabled) {
globals.analytics.send(Event.analyticsCollectionEnabled(status: true));
}
globals.printStatus('Analytics reporting enabled.');
}
await runner.run(args);
// Triggering [runZoned]'s error callback does not necessarily mean that
// we stopped executing the body. See https://github.com/dart-lang/sdk/issues/42150.
if (firstError == null) {
return await exitWithHooks(0, shutdownHooks: shutdownHooks);
}
// We already hit some error, so don't return success. The error path
// (which should be in progress) is responsible for calling _exit().
return 1;
// This catches all exceptions to send to crash logging, etc.
// ignore: avoid_catches_without_on_clauses
} catch (error, stackTrace) {
firstError = error;
firstStackTrace = stackTrace;
return _handleToolError(
error,
stackTrace,
verbose,
args,
reportCrashes!,
getVersion,
shutdownHooks,
featureFlags: featureFlags,
usingLocalEngine: usingLocalEngine,
);
}
},
(Object error, StackTrace stackTrace) async {
// If sending a crash report throws an error into the zone, we don't want
// to re-try sending the crash report with *that* error. Rather, we want
// to send the original error that triggered the crash report.
firstError ??= error;
firstStackTrace ??= stackTrace;
await _handleToolError(
firstError!,
firstStackTrace,
verbose,
args,
reportCrashes!,
getVersion,
shutdownHooks,
featureFlags: featureFlags,
usingLocalEngine: usingLocalEngine,
);
},
)!;
}, overrides: overrides);
}
Future<int> _handleToolError(
Object error,
StackTrace? stackTrace,
bool verbose,
List<String> args,
bool reportCrashes,
String Function() getFlutterVersion,
ShutdownHooks shutdownHooks, {
required bool usingLocalEngine,
required FeatureFlags featureFlags,
}) async {
if (error is UsageException) {
globals.printError('${error.message}\n');
globals.printError(
"Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and options.",
);
// Argument error exit code.
return exitWithHooks(64, shutdownHooks: shutdownHooks);
} else if (error is ToolExit) {
if (error.message != null) {
globals.printError(error.message!);
}
if (verbose) {
globals.printError('\n$stackTrace\n');
}
return exitWithHooks(error.exitCode ?? 1, shutdownHooks: shutdownHooks);
} else if (error is ProcessExit) {
// We've caught an exit code.
if (error.immediate) {
exit(error.exitCode);
return error.exitCode;
} else {
return exitWithHooks(error.exitCode, shutdownHooks: shutdownHooks);
}
} else if (error is ProcessException &&
_isErrorDueToGitMissing(error, globals.processManager, globals.logger)) {
globals.printError('${error.message}\n');
globals.printError(
'An error was encountered when trying to run git.\n'
"Please ensure git is installed and available in your system's search path. "
'See https://docs.flutter.dev/get-started for instructions on '
'installing git for your platform.',
);
return exitWithHooks(1, shutdownHooks: shutdownHooks);
} else {
// We've crashed; emit a log report.
globals.stdio.stderrWrite('\n');
if (!reportCrashes) {
// Print the stack trace on the bots - don't write a crash report.
globals.stdio.stderrWrite('$error\n');
globals.stdio.stderrWrite('$stackTrace\n');
final String featureFlagsEnabled = allConfigurableFeatures
.where(featureFlags.isEnabled)
.map((Feature f) => f.configSetting)
.join(', ');
globals.stdio.stderrWrite('Feature flags enabled: $featureFlagsEnabled\n');
return exitWithHooks(1, shutdownHooks: shutdownHooks);
}
globals.analytics.send(Event.exception(exception: error.runtimeType.toString()));
if (!usingLocalEngine) {
await asyncGuard(
() async {
final crashReportSender = CrashReportSender(
platform: globals.platform,
logger: globals.logger,
operatingSystemUtils: globals.os,
analytics: globals.analytics,
);
await crashReportSender.sendReport(
error: error,
stackTrace: stackTrace!,
getFlutterVersion: getFlutterVersion,
command: args.join(' '),
);
},
onError: (dynamic error) {
globals.printError('Error sending crash report: $error');
},
);
}
globals.printError('Oops; flutter has exited unexpectedly: "$error".');
try {
final logger = BufferLogger(
terminal: globals.terminal,
outputPreferences: globals.outputPreferences,
verbose: true /* Capture flutter doctor -v */,
);
final doctorText = DoctorText(logger);
final details = CrashDetails(
command: _crashCommand(args),
error: error,
stackTrace: stackTrace!,
doctorText: doctorText,
);
final File file = await _createLocalCrashReport(details);
await globals.crashReporter!.informUser(details, file);
return exitWithHooks(1, shutdownHooks: shutdownHooks);
// This catch catches all exceptions to ensure the message below is printed.
// ignore: avoid_catches_without_on_clauses
} catch (error, st) {
globals.stdio.stderrWrite(
'Unable to generate crash report due to secondary error: $error\n$st\n'
'${globals.userMessages.flutterToolBugInstructions}\n',
);
// Any exception thrown here (including one thrown by `_exit()`) will
// get caught by our zone's `onError` handler. In order to avoid an
// infinite error loop, we throw an error that is recognized above
// and will trigger an immediate exit.
throw ProcessExit(1, immediate: true);
}
}
}
String _crashCommand(List<String> args) => 'flutter ${args.join(' ')}';
String _crashException(dynamic error) => '${error.runtimeType}: $error';
/// Saves the crash report to a local file.
Future<File> _createLocalCrashReport(CrashDetails details) async {
final buffer = StringBuffer();
buffer.writeln('Flutter crash report.');
buffer.writeln('${globals.userMessages.flutterToolBugInstructions}\n');
buffer.writeln('## command\n');
buffer.writeln('${details.command}\n');
buffer.writeln('## exception\n');
buffer.writeln('${_crashException(details.error)}\n');
buffer.writeln('```\n${details.stackTrace}```\n');
buffer.writeln('## flutter doctor\n');
buffer.writeln('```\n${await details.doctorText.text}```');
late File crashFile;
ErrorHandlingFileSystem.noExitOnFailure(() {
try {
crashFile = globals.fsUtils.getUniqueFile(globals.fs.currentDirectory, 'flutter', 'log');
crashFile.writeAsStringSync(buffer.toString());
} on FileSystemException catch (_) {
// Fallback to the system temporary directory.
try {
crashFile = globals.fsUtils.getUniqueFile(globals.fs.systemTempDirectory, 'flutter', 'log');
crashFile.writeAsStringSync(buffer.toString());
} on FileSystemException catch (e) {
globals.printError('Could not write crash report to disk: $e');
globals.printError(buffer.toString());
rethrow;
}
}
});
return crashFile;
}
bool _isErrorDueToGitMissing(
ProcessException exception,
ProcessManager processManager,
Logger logger,
) {
if (!exception.message.contains('git')) {
return false;
}
try {
return !processManager.canRun('git');
} on Object catch (error) {
logger.printTrace('Unable to check whether git is runnable: $error\n');
return true;
}
}