| // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
| // for details. 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 'dart:io'; | |
| import 'dart:isolate'; | |
| import 'dart:math'; | |
| import 'package:analysis_server/protocol/protocol_constants.dart' | |
| show PROTOCOL_VERSION; | |
| import 'package:analysis_server/src/analytics/analytics_manager.dart'; | |
| import 'package:analysis_server/src/legacy_analysis_server.dart'; | |
| import 'package:analysis_server/src/lsp/lsp_socket_server.dart'; | |
| import 'package:analysis_server/src/server/crash_reporting.dart'; | |
| import 'package:analysis_server/src/server/crash_reporting_attachments.dart'; | |
| import 'package:analysis_server/src/server/detachable_filesystem_manager.dart'; | |
| import 'package:analysis_server/src/server/dev_server.dart'; | |
| import 'package:analysis_server/src/server/diagnostic_server.dart'; | |
| import 'package:analysis_server/src/server/error_notifier.dart'; | |
| import 'package:analysis_server/src/server/features.dart'; | |
| import 'package:analysis_server/src/server/http_server.dart'; | |
| import 'package:analysis_server/src/server/isolate_analysis_server.dart'; | |
| import 'package:analysis_server/src/server/lsp_stdio_server.dart'; | |
| import 'package:analysis_server/src/server/sdk_configuration.dart'; | |
| import 'package:analysis_server/src/server/stdio_server.dart'; | |
| import 'package:analysis_server/src/services/correction/assist_internal.dart'; | |
| import 'package:analysis_server/src/services/correction/fix_internal.dart'; | |
| import 'package:analysis_server/src/services/perf_witness/perf_witness.dart'; | |
| import 'package:analysis_server/src/session_logger/session_logger.dart'; | |
| import 'package:analysis_server/src/socket_server.dart'; | |
| import 'package:analysis_server/src/status/performance_logger.dart'; | |
| import 'package:analysis_server/src/utilities/env.dart' as env; | |
| import 'package:analysis_server/src/utilities/request_statistics.dart'; | |
| import 'package:analysis_server/starter.dart'; | |
| import 'package:analyzer/file_system/physical_file_system.dart'; | |
| import 'package:analyzer/instrumentation/file_instrumentation.dart'; | |
| import 'package:analyzer/instrumentation/instrumentation.dart'; | |
| import 'package:analyzer/src/dart/sdk/sdk.dart'; | |
| import 'package:analyzer/src/generated/engine.dart'; | |
| import 'package:analyzer/src/generated/sdk.dart'; | |
| import 'package:analyzer/src/util/sdk.dart'; | |
| import 'package:args/args.dart'; | |
| import 'package:linter/src/rules.dart' as linter; | |
| import 'package:telemetry/crash_reporting.dart'; | |
| import 'package:unified_analytics/unified_analytics.dart'; | |
| import '../utilities/usage_tracking/usage_tracking.dart'; | |
| /// The [Driver] class represents a single running instance of the analysis | |
| /// server application. It is responsible for parsing command line options | |
| /// and starting the HTTP and/or stdio servers. | |
| class Driver implements ServerStarter { | |
| /// The name of the application that is used to start a server. | |
| static const binaryName = 'analysis_server'; | |
| /// The name of the option used to set the identifier for the client. | |
| static const String clientIdOption = 'client-id'; | |
| /// The name of the option used to set the version for the client. | |
| static const String clientVersionOption = 'client-version'; | |
| /// The name of the option used to disable exception handling. | |
| static const String disableServerExceptionHandlingOption = | |
| 'disable-server-exception-handling'; | |
| /// The name of the option to disable the completion feature. | |
| static const String disableServerFeatureCompletionOption = | |
| 'disable-server-feature-completion'; | |
| /// The name of the option to disable the search feature. | |
| static const String disableServerFeatureSearchOption = | |
| 'disable-server-feature-search'; | |
| /// The name of the option to disable the debouncing of `server.status` | |
| /// notifications. | |
| static const String disableStatusNotificationDebouncingOption = | |
| 'disable-status-notification-debouncing'; | |
| /// The name of the option to prevent exceptions during analysis from being | |
| /// silent. | |
| static const String disableSilentAnalysisExceptionsOption = | |
| 'disable-silent-analysis-exceptions'; | |
| /// The name of the multi-option to enable one or more experiments. | |
| static const String enableExperimentOption = 'enable-experiment'; | |
| /// The name of the option used to print usage information. | |
| static const String helpFlag = 'help'; | |
| /// The name of the flag used to configure reporting legacy analytics. | |
| static const String analyticsFlag = 'analytics'; | |
| /// The name of the flag used to suppress legacy analytics for this session. | |
| static const String suppressAnalyticsFlag = 'suppress-analytics'; | |
| /// The name of the option used to cause instrumentation to also be written to | |
| /// a local file. | |
| static const String protocolTrafficLogOption = 'protocol-traffic-log'; | |
| static const String protocolTrafficLogAliasOption = | |
| 'instrumentation-log-file'; | |
| /// The name of the option used to cause a log of the session to be written to | |
| /// a file. | |
| static const String sessionLogOption = 'session-log'; | |
| /// The name of the option used to cause selected data from the analysis | |
| /// server to be written to a file. | |
| static const String performanceLogOption = 'performance-log'; | |
| /// The name of the option used to specify if [print] should print to the | |
| /// console instead of being intercepted. | |
| static const String internalPrintToConsoleOption = | |
| 'internal-print-to-console'; | |
| /// The name of the option used to describe the new analysis driver logger. | |
| static const String analysisDriverLogOption = 'analysis-driver-log'; | |
| static const String analysisDriverLogAliasOption = 'new-analysis-driver-log'; | |
| /// The name of the option for specifying the http diagnostic port. | |
| /// If specified, users can review server status and performance information | |
| /// by opening a web browser on `http://localhost:<port>`. | |
| static const String diagnosticPortOption = 'diagnostic-port'; | |
| static const String diagnosticPortAliasOption = 'port'; | |
| /// The name of the option used to specify the path to the SDK. | |
| static const String dartSdkOption = 'dart-sdk'; | |
| static const String dartSdkAliasOption = 'sdk'; | |
| /// The name of the option used to specify the path to the data cache. | |
| static const String cacheFolderOption = 'cache'; | |
| /// The name of the option used to specify the path to the package config file | |
| /// override. | |
| static const String packagesFileOption = 'packages'; | |
| /// The name of the option used to specify a forced protocol version that the | |
| /// server will report to the client. | |
| static const String reportProtocolVersionOption = 'report-protocol-version'; | |
| /// The name of the option specifying the server protocol to use. | |
| static const String serverProtocolOption = 'protocol'; | |
| static const String protocolAnalyzer = 'analyzer'; | |
| static const String protocolLsp = 'lsp'; | |
| /// The name of the flag to use the Language Server Protocol (LSP). | |
| static const String useLspFlag = 'lsp'; | |
| /// The name of the flag used to specify a directory to analyze in order to | |
| /// train an analysis server snapshot. | |
| static const String trainUsingOption = 'train-using'; | |
| /// Flag to not use a (Evicting)FileByteStore. | |
| static const String disableFileByteStoreOption = 'disable-file-byte-store'; | |
| /// The name of the flag to enable fine-grained dependencies. | |
| static const String withFineDependenciesOption = 'with-fine-dependencies'; | |
| static const String pluginsFlag = 'plugins'; | |
| /// The builder for attachments that should be included into crash reports. | |
| CrashReportingAttachmentsBuilder crashReportingAttachmentsBuilder = | |
| CrashReportingAttachmentsBuilder.empty; | |
| /// An optional manager to handle file systems which may not always be | |
| /// available. | |
| DetachableFileSystemManager? detachableFileSystemManager; | |
| /// The instrumentation service that is to be used by the analysis server. | |
| late final InstrumentationService _instrumentationService; | |
| /// The session logger. | |
| late final SessionLogger _sessionLogger; | |
| /// Use the given command-line [arguments] to start this server. | |
| /// | |
| /// If [sendPort] is not null, assumes this is launched in an isolate and will | |
| /// connect to the original isolate via an isolate channel. | |
| @override | |
| void start( | |
| List<String> arguments, { | |
| SendPort? sendPort, | |
| bool defaultToLsp = false, | |
| }) async { | |
| await startPerfWitness(); | |
| var sessionStartTime = DateTime.now(); | |
| var parser = createArgParser(defaultToLsp: defaultToLsp); | |
| var results = parser.parse(arguments); | |
| var analysisServerOptions = AnalysisServerOptions(); | |
| analysisServerOptions.newAnalysisDriverLog = | |
| results.option(analysisDriverLogOption) ?? | |
| results.option(analysisDriverLogAliasOption); | |
| if (results.wasParsed(useLspFlag)) { | |
| analysisServerOptions.useLanguageServerProtocol = results.flag( | |
| useLspFlag, | |
| ); | |
| } else { | |
| analysisServerOptions.useLanguageServerProtocol = | |
| results.option(serverProtocolOption) == protocolLsp; | |
| } | |
| // For clients that don't supply their own identifier, use a default based | |
| // on whether the server will run in LSP mode or not. | |
| var clientId = | |
| results.option(clientIdOption) ?? | |
| (analysisServerOptions.useLanguageServerProtocol | |
| ? 'unknown.client.lsp' | |
| : 'unknown.client.classic'); | |
| analysisServerOptions.clientId = clientId; | |
| analysisServerOptions.clientVersion = results.option(clientVersionOption); | |
| analysisServerOptions.cacheFolder = results.option(cacheFolderOption); | |
| analysisServerOptions.packagesFile = results.option(packagesFileOption); | |
| analysisServerOptions.reportProtocolVersion = results.option( | |
| reportProtocolVersionOption, | |
| ); | |
| analysisServerOptions.withFineDependencies = results.flag( | |
| withFineDependenciesOption, | |
| ); | |
| analysisServerOptions.enabledExperiments = results.multiOption( | |
| enableExperimentOption, | |
| ); | |
| // Read in any per-SDK overrides specified in <sdk>/config/settings.json. | |
| var sdkConfig = SdkConfiguration.readFromSdk(); | |
| analysisServerOptions.configurationOverrides = sdkConfig; | |
| analysisServerOptions.usePlugins = results.flag(pluginsFlag); | |
| var defaultSdkPath = _getSdkPath(results); | |
| var dartSdkManager = DartSdkManager(defaultSdkPath); | |
| // TODO(brianwilkerson): It would be nice to avoid creating an SDK that | |
| // can't be re-used, but the SDK is needed to create a package map provider | |
| // in the case where we need to run `pub` in order to get the package map. | |
| var defaultSdk = _createDefaultSdk(defaultSdkPath); | |
| // Create the analytics manager. | |
| var tool = | |
| env.topLevelTool ?? | |
| switch (clientId) { | |
| 'VS-Code' || 'VS-Code-Remote' => DashTool.vscodePlugins, | |
| 'IntelliJ-IDEA' => DashTool.intellijPlugins, | |
| 'Android-Studio' => DashTool.androidStudioPlugins, | |
| _ => null, | |
| }; | |
| // Analytics (legacy, and unified). | |
| var disableAnalyticsForSession = | |
| tool == null || | |
| areAnalyticsSuppressed() || | |
| results.flag(suppressAnalyticsFlag) || | |
| results.wasParsed(trainUsingOption); | |
| var analytics = disableAnalyticsForSession | |
| ? NoOpAnalytics() | |
| : _createAnalytics(defaultSdk, defaultSdkPath, tool); | |
| var analyticsManager = AnalyticsManager(analytics); | |
| bool shouldSendCallback() { | |
| // Check sdkConfig to optionally force reporting on. | |
| if (sdkConfig.crashReportingForceEnabled == true) { | |
| return true; | |
| } | |
| // Reuse the unified_analytics consent mechanism to determine whether | |
| // we can send a crash report. | |
| return analyticsManager.analytics.okToSend; | |
| } | |
| // Crash reporting | |
| // Use sdkConfig to optionally override analytics settings. | |
| var crashProductId = sdkConfig.crashReportingId ?? 'Dart_analysis_server'; | |
| var crashReportSender = CrashReportSender.prod( | |
| crashProductId, | |
| shouldSendCallback, | |
| ); | |
| { | |
| var disableCompletion = results.flag( | |
| disableServerFeatureCompletionOption, | |
| ); | |
| var disableSearch = results.flag(disableServerFeatureSearchOption); | |
| if (disableCompletion || disableSearch) { | |
| analysisServerOptions.featureSet = FeatureSet( | |
| completion: !disableCompletion, | |
| search: !disableSearch, | |
| ); | |
| } | |
| } | |
| if (results.flag(helpFlag)) { | |
| _printUsage(parser, fromHelp: true); | |
| return; | |
| } | |
| // Record the start of the session. | |
| analyticsManager.startUp( | |
| time: sessionStartTime, | |
| arguments: _getArgumentsForAnalytics(results), | |
| clientId: clientId, | |
| clientVersion: analysisServerOptions.clientVersion, | |
| ); | |
| // Initialize the instrumentation service. | |
| var logFilePath = | |
| results.option(protocolTrafficLogOption) ?? | |
| results.option(protocolTrafficLogAliasOption); | |
| var allInstrumentationServices = <InstrumentationService>[]; | |
| if (logFilePath != null) { | |
| _rollLogFiles(logFilePath, 5); | |
| allInstrumentationServices.add( | |
| InstrumentationLogAdapter( | |
| FileInstrumentationLogger(logFilePath), | |
| watchEventExclusionFiles: {logFilePath}, | |
| ), | |
| ); | |
| } | |
| var errorNotifier = ErrorNotifier(); | |
| allInstrumentationServices.add(errorNotifier); | |
| allInstrumentationServices.add( | |
| CrashReportingInstrumentation(crashReportSender), | |
| ); | |
| _instrumentationService = MulticastInstrumentationService( | |
| allInstrumentationServices, | |
| ); | |
| // Initialize the session logging service. | |
| var sessionLogFilePath = results.option(sessionLogOption); | |
| _sessionLogger = SessionLogger(filePath: sessionLogFilePath); | |
| _sessionLogger.normalizer.addReplacementsForPath( | |
| defaultSdkPath, | |
| 'dartSdkRoot', | |
| ); | |
| // Log the command-line. | |
| _instrumentationService.logVersion( | |
| results.option(trainUsingOption) != null | |
| ? 'training-0' | |
| : _readUuid(_instrumentationService, _sessionLogger), | |
| analysisServerOptions.clientId ?? '', | |
| analysisServerOptions.clientVersion ?? '', | |
| PROTOCOL_VERSION, | |
| defaultSdk.languageVersion.toString(), | |
| ); | |
| AnalysisEngine.instance.instrumentationService = _instrumentationService; | |
| _sessionLogger.logCommandLine(arguments: arguments); | |
| int? diagnosticServerPort; | |
| var portValue = | |
| results.option(diagnosticPortOption) ?? | |
| results.option(diagnosticPortAliasOption); | |
| if (portValue != null) { | |
| try { | |
| diagnosticServerPort = int.parse(portValue); | |
| } on FormatException { | |
| print('Invalid port number: $portValue'); | |
| print(''); | |
| _printUsage(parser); | |
| exitCode = 1; | |
| return; | |
| } | |
| } | |
| PerformanceLogger? performanceLogger; | |
| var performanceLogFilePath = results.option(performanceLogOption); | |
| if (performanceLogFilePath != null) { | |
| _rollLogFiles(performanceLogFilePath, 5); | |
| performanceLogger = PerformanceLogger(performanceLogFilePath); | |
| } | |
| // TODO(brianwilkerson): Pass the following value to the server and | |
| // implement the debouncing when it hasn't been disabled. | |
| // var disableDebouncing = results[DISABLE_STATUS_NOTIFICATION_DEBOUNCING] as bool; | |
| var environment = env.map( | |
| tool: tool, | |
| suppressAnalytics: disableAnalyticsForSession, | |
| ); | |
| if (analysisServerOptions.useLanguageServerProtocol) { | |
| if (sendPort != null) { | |
| throw UnimplementedError( | |
| 'Isolate usage not implemented for LspAnalysisServer', | |
| ); | |
| } | |
| startLspServer( | |
| results, | |
| analysisServerOptions, | |
| dartSdkManager, | |
| analyticsManager, | |
| _instrumentationService, | |
| _sessionLogger, | |
| diagnosticServerPort, | |
| errorNotifier, | |
| performanceLogger, | |
| environment, | |
| ); | |
| } else { | |
| startAnalysisServer( | |
| results, | |
| analysisServerOptions, | |
| parser, | |
| dartSdkManager, | |
| analyticsManager, | |
| crashReportingAttachmentsBuilder, | |
| _instrumentationService, | |
| _sessionLogger, | |
| RequestStatisticsHelper(), | |
| diagnosticServerPort, | |
| errorNotifier, | |
| sendPort, | |
| performanceLogger, | |
| environment, | |
| ); | |
| } | |
| configureMemoryUsageTracking( | |
| arguments, | |
| (memoryUsageEvent) => analyticsManager.sendMemoryUsage(memoryUsageEvent), | |
| ); | |
| } | |
| void startAnalysisServer( | |
| ArgResults results, | |
| AnalysisServerOptions analysisServerOptions, | |
| ArgParser parser, | |
| DartSdkManager dartSdkManager, | |
| AnalyticsManager analyticsManager, | |
| CrashReportingAttachmentsBuilder crashReportingAttachmentsBuilder, | |
| InstrumentationService instrumentationService, | |
| SessionLogger sessionLogger, | |
| RequestStatisticsHelper requestStatistics, | |
| int? diagnosticServerPort, | |
| ErrorNotifier errorNotifier, | |
| SendPort? sendPort, | |
| PerformanceLogger? performanceLogger, | |
| Map<String, String>? environment, | |
| ) { | |
| var capture = results.flag(disableServerExceptionHandlingOption) | |
| ? (_, _, Function f, {void Function(String)? print}) => f() | |
| : _captureExceptions; | |
| var trainDirectory = results.option(trainUsingOption); | |
| if (trainDirectory != null) { | |
| if (!FileSystemEntity.isDirectorySync(trainDirectory)) { | |
| print("Training directory '$trainDirectory' not found.\n"); | |
| exitCode = 1; | |
| return; | |
| } | |
| } | |
| linter.registerLintRules(); | |
| registerBuiltInAssistGenerators(); | |
| registerBuiltInFixGenerators(); | |
| var diagnosticServer = _DiagnosticServerImpl(); | |
| // | |
| // Create the sockets and start listening for requests. | |
| // | |
| var socketServer = SocketServer( | |
| analysisServerOptions, | |
| dartSdkManager, | |
| crashReportingAttachmentsBuilder, | |
| instrumentationService, | |
| sessionLogger, | |
| requestStatistics, | |
| diagnosticServer, | |
| analyticsManager, | |
| detachableFileSystemManager, | |
| performanceLogger, | |
| environment: environment, | |
| ); | |
| diagnosticServer.httpServer = HttpAnalysisServer(socketServer); | |
| if (diagnosticServerPort != null) { | |
| diagnosticServer.startOnPort(diagnosticServerPort); | |
| } | |
| if (trainDirectory != null) { | |
| if (sendPort != null) { | |
| throw UnimplementedError( | |
| 'isolate usage not supported for DevAnalysisServer', | |
| ); | |
| } | |
| var tempDriverDir = Directory.systemTemp.createTempSync( | |
| 'analysis_server_', | |
| ); | |
| analysisServerOptions.cacheFolder = tempDriverDir.path; | |
| analysisServerOptions.disableFileByteStore = results.flag( | |
| disableFileByteStoreOption, | |
| ); | |
| var devServer = DevAnalysisServer(socketServer); | |
| devServer.initServer(); | |
| () async { | |
| // We first analyze code with an empty driver cache. | |
| print('Analyzing with an empty driver cache:'); | |
| var exitCode = await devServer.processDirectories([trainDirectory]); | |
| if (exitCode != 0) exit(exitCode); | |
| print(''); | |
| // Then again with a populated cache. | |
| print('Analyzing with a populated driver cache:'); | |
| exitCode = await devServer.processDirectories([trainDirectory]); | |
| if (exitCode != 0) exit(exitCode); | |
| diagnosticServer.httpServer.close(); | |
| await instrumentationService.shutdown(); | |
| unawaited(socketServer.analysisServer!.shutdown()); | |
| try { | |
| tempDriverDir.deleteSync(recursive: true); | |
| } catch (_) { | |
| // Ignore any exception. | |
| } | |
| exit(exitCode); | |
| }(); | |
| } else { | |
| capture( | |
| instrumentationService, | |
| sessionLogger, | |
| () { | |
| Future<void> serveResult; | |
| if (sendPort == null) { | |
| var stdioServer = StdioAnalysisServer(socketServer); | |
| serveResult = stdioServer.serveStdio(); | |
| } else { | |
| var isolateAnalysisServer = IsolateAnalysisServer(socketServer); | |
| serveResult = isolateAnalysisServer.serveIsolate(sendPort); | |
| } | |
| errorNotifier.server = socketServer.analysisServer; | |
| if (results.flag(disableSilentAnalysisExceptionsOption)) { | |
| errorNotifier.sendSilentExceptionsToClient = true; | |
| } | |
| serveResult.then((_) async { | |
| diagnosticServer.httpServer.close(); | |
| await instrumentationService.shutdown(); | |
| unawaited(socketServer.analysisServer!.shutdown()); | |
| if (sendPort == null) exit(0); | |
| }); | |
| }, | |
| print: results.flag(internalPrintToConsoleOption) | |
| ? null | |
| : diagnosticServer.httpServer.recordPrint, | |
| ); | |
| } | |
| } | |
| void startLspServer( | |
| ArgResults args, | |
| AnalysisServerOptions analysisServerOptions, | |
| DartSdkManager dartSdkManager, | |
| AnalyticsManager analyticsManager, | |
| InstrumentationService instrumentationService, | |
| SessionLogger sessionLogger, | |
| int? diagnosticServerPort, | |
| ErrorNotifier errorNotifier, | |
| PerformanceLogger? performanceLogger, | |
| Map<String, String>? environment, | |
| ) { | |
| var capture = args.flag(disableServerExceptionHandlingOption) | |
| ? (_, _, Function f, {void Function(String)? print}) => f() | |
| : _captureExceptions; | |
| linter.registerLintRules(); | |
| registerBuiltInAssistGenerators(); | |
| registerBuiltInFixGenerators(); | |
| var diagnosticServer = _DiagnosticServerImpl(); | |
| var socketServer = LspSocketServer( | |
| analysisServerOptions, | |
| diagnosticServer, | |
| analyticsManager, | |
| dartSdkManager, | |
| instrumentationService, | |
| sessionLogger, | |
| detachableFileSystemManager, | |
| performanceLogger, | |
| environment: environment, | |
| ); | |
| diagnosticServer.httpServer = HttpAnalysisServer(socketServer); | |
| if (diagnosticServerPort != null) { | |
| diagnosticServer.startOnPort(diagnosticServerPort); | |
| } | |
| capture(instrumentationService, sessionLogger, () { | |
| var stdioServer = LspStdioAnalysisServer(socketServer); | |
| var serveResult = stdioServer.serveStdio(); | |
| errorNotifier.server = socketServer.analysisServer; | |
| if (args.flag(disableSilentAnalysisExceptionsOption)) { | |
| errorNotifier.sendSilentExceptionsToClient = true; | |
| } | |
| serveResult.then((_) { | |
| // Only shutdown the server and exit if the server is not already | |
| // handling the shutdown. | |
| if (!socketServer.analysisServer!.willExit) { | |
| unawaited(socketServer.analysisServer!.shutdown()); | |
| exit(0); | |
| } | |
| }); | |
| }); | |
| } | |
| /// Execute the given [callback] within a zone that will capture any unhandled | |
| /// exceptions and both report them to the client and send them to the given | |
| /// instrumentation [service]. If a [print] function is provided, then also | |
| /// capture any data printed by the callback and redirect it to the function. | |
| void _captureExceptions( | |
| InstrumentationService service, | |
| SessionLogger sessionLogger, | |
| void Function() callback, { | |
| void Function(String line)? print, | |
| }) { | |
| void errorFunction( | |
| Zone self, | |
| ZoneDelegate parent, | |
| Zone zone, | |
| Object exception, | |
| StackTrace stackTrace, | |
| ) { | |
| service.logException(exception, stackTrace); | |
| sessionLogger.logException(exception: exception, stackTrace: stackTrace); | |
| throw exception; | |
| } | |
| var printFunction = print == null | |
| ? null | |
| : (Zone self, ZoneDelegate parent, Zone zone, String line) { | |
| // Note: we don't pass the line on to stdout, because that is | |
| // reserved for communication to the client. | |
| print(line); | |
| }; | |
| var zoneSpecification = ZoneSpecification( | |
| handleUncaughtError: errorFunction, | |
| print: printFunction, | |
| ); | |
| return runZoned(callback, zoneSpecification: zoneSpecification); | |
| } | |
| /// Create the `Analytics` instance to be used to report analytics. | |
| Analytics _createAnalytics( | |
| DartSdk dartSdk, | |
| String dartSdkPath, | |
| DashTool tool, | |
| ) { | |
| // TODO(brianwilkerson): Find out whether there's a way to get the channel | |
| // without running `flutter channel`. | |
| var pathContext = PhysicalResourceProvider.INSTANCE.pathContext; | |
| var flutterSdkRoot = pathContext.dirname( | |
| pathContext.dirname(pathContext.dirname(dartSdkPath)), | |
| ); | |
| var flutterVersionFile = PhysicalResourceProvider.INSTANCE.getFile( | |
| pathContext.join(flutterSdkRoot, 'version'), | |
| ); | |
| String? flutterVersion; | |
| try { | |
| flutterVersion = flutterVersionFile.readAsStringSync(); | |
| } catch (exception) { | |
| // If we can't read the file, ignore it. | |
| } | |
| return Analytics( | |
| tool: tool, | |
| dartVersion: dartSdk.sdkVersion, | |
| // flutterChannel: '', | |
| flutterVersion: flutterVersion, | |
| ); | |
| } | |
| DartSdk _createDefaultSdk(String defaultSdkPath) { | |
| var resourceProvider = PhysicalResourceProvider.INSTANCE; | |
| return FolderBasedDartSdk( | |
| resourceProvider, | |
| resourceProvider.getFolder(defaultSdkPath), | |
| ); | |
| } | |
| /// Constructs a uuid combining the current date and a random integer. | |
| String _generateUuidString() { | |
| var millisecondsSinceEpoch = DateTime.now().millisecondsSinceEpoch; | |
| var random = Random().nextInt(0x3fffffff); | |
| return '$millisecondsSinceEpoch$random'; | |
| } | |
| /// Return a list of the known command-line arguments that were parsed when | |
| /// creating the given [results]. We return only the names of the arguments, | |
| /// not the values of the arguments, in order to prevent the collection of any | |
| /// PII. | |
| List<String> _getArgumentsForAnalytics(ArgResults results) { | |
| // The arguments that are literal strings are deprecated and no longer read | |
| // from, but we still want to know if clients are using them. | |
| var knownArguments = [ | |
| analyticsFlag, | |
| cacheFolderOption, | |
| clientIdOption, | |
| clientVersionOption, | |
| 'completion-model', | |
| 'dartpad', | |
| dartSdkOption, | |
| dartSdkAliasOption, | |
| diagnosticPortOption, | |
| diagnosticPortAliasOption, | |
| disableServerExceptionHandlingOption, | |
| disableServerFeatureCompletionOption, | |
| disableServerFeatureSearchOption, | |
| disableSilentAnalysisExceptionsOption, | |
| disableStatusNotificationDebouncingOption, | |
| 'enable-completion-model', | |
| 'enable-experiment', | |
| 'enable-instrumentation', | |
| 'file-read-mode', | |
| helpFlag, | |
| 'ignore-unrecognized-flags', | |
| internalPrintToConsoleOption, | |
| packagesFileOption, | |
| 'preview-dart-2', | |
| protocolTrafficLogOption, | |
| protocolTrafficLogAliasOption, | |
| reportProtocolVersionOption, | |
| serverProtocolOption, | |
| sessionLogOption, | |
| suppressAnalyticsFlag, | |
| trainUsingOption, | |
| 'useAnalysisHighlight2', | |
| useLspFlag, | |
| 'use-new-relevance', | |
| 'use-fasta-parser', | |
| disableFileByteStoreOption, | |
| withFineDependenciesOption, | |
| ]; | |
| return knownArguments | |
| .where((argument) => results.wasParsed(argument)) | |
| .toList(); | |
| } | |
| String _getSdkPath(ArgResults args) { | |
| String? sdkPath; | |
| void tryCandidateArgument(String argumentName) { | |
| var argumentValue = args[argumentName]; | |
| if (sdkPath == null && argumentValue is String) { | |
| sdkPath = argumentValue; | |
| } | |
| } | |
| tryCandidateArgument(dartSdkOption); | |
| tryCandidateArgument(dartSdkAliasOption); | |
| var sdkPath2 = sdkPath ?? getSdkPath(); | |
| var pathContext = PhysicalResourceProvider.INSTANCE.pathContext; | |
| return pathContext.normalize(pathContext.absolute(sdkPath2)); | |
| } | |
| /// Print information about how to use the server. | |
| void _printUsage(ArgParser parser, {bool fromHelp = false}) { | |
| print('Usage: $binaryName [flags]'); | |
| print(''); | |
| print('Supported flags are:'); | |
| print(parser.usage); | |
| } | |
| /// Read the UUID from disk, generating and storing a new one if necessary. | |
| String _readUuid( | |
| InstrumentationService service, | |
| SessionLogger sessionLogger, | |
| ) { | |
| var instrumentationLocation = PhysicalResourceProvider.INSTANCE | |
| .getStateLocation('.instrumentation'); | |
| if (instrumentationLocation == null) { | |
| return _generateUuidString(); | |
| } | |
| var uuidFile = File(instrumentationLocation.getChild('uuid.txt').path); | |
| try { | |
| if (uuidFile.existsSync()) { | |
| var uuid = uuidFile.readAsStringSync(); | |
| if (uuid.length > 5) { | |
| return uuid; | |
| } | |
| } | |
| } catch (exception, stackTrace) { | |
| service.logException(exception, stackTrace); | |
| sessionLogger.logException(exception: exception, stackTrace: stackTrace); | |
| } | |
| var uuid = _generateUuidString(); | |
| try { | |
| uuidFile.parent.createSync(recursive: true); | |
| uuidFile.writeAsStringSync(uuid); | |
| } catch (exception, stackTrace) { | |
| service.logException(exception, stackTrace); | |
| sessionLogger.logException(exception: exception, stackTrace: stackTrace); | |
| // Slightly alter the uuid to indicate it was not persisted | |
| uuid = 'temp-$uuid'; | |
| } | |
| return uuid; | |
| } | |
| /// Create and return the parser used to parse the command-line arguments. | |
| static ArgParser createArgParser({ | |
| int? usageLineLength, | |
| bool includeHelpFlag = true, | |
| bool defaultToLsp = false, | |
| }) { | |
| var parser = ArgParser(usageLineLength: usageLineLength); | |
| if (includeHelpFlag) { | |
| parser.addFlag( | |
| helpFlag, | |
| abbr: 'h', | |
| negatable: false, | |
| help: 'Print this usage information.', | |
| ); | |
| } | |
| parser.addOption( | |
| clientIdOption, | |
| valueHelp: 'name', | |
| help: 'An identifier for the analysis server client.', | |
| ); | |
| parser.addOption( | |
| clientVersionOption, | |
| valueHelp: 'version', | |
| help: 'The version of the analysis server client.', | |
| ); | |
| parser.addOption( | |
| dartSdkOption, | |
| valueHelp: 'path', | |
| help: 'Override the Dart SDK to use for analysis.', | |
| ); | |
| parser.addOption(dartSdkAliasOption, hide: true); | |
| parser.addOption( | |
| cacheFolderOption, | |
| valueHelp: 'path', | |
| help: 'Override the location of the analysis server\'s cache.', | |
| ); | |
| parser.addOption( | |
| packagesFileOption, | |
| valueHelp: 'path', | |
| help: | |
| 'The path to the package resolution configuration file, which ' | |
| 'supplies a mapping of package names\ninto paths.', | |
| ); | |
| parser.addMultiOption( | |
| enableExperimentOption, | |
| valueHelp: 'experiment', | |
| help: | |
| 'Enable one or more experimental features ' | |
| '(see dart.dev/go/experiments).', | |
| hide: true, | |
| ); | |
| parser.addOption( | |
| serverProtocolOption, | |
| defaultsTo: defaultToLsp ? protocolLsp : protocolAnalyzer, | |
| valueHelp: 'protocol', | |
| allowed: [protocolLsp, protocolAnalyzer], | |
| allowedHelp: { | |
| protocolLsp: | |
| 'The Language Server Protocol ' | |
| '(https://microsoft.github.io/language-server-protocol)', | |
| protocolAnalyzer: | |
| 'Dart\'s analysis server protocol ' | |
| '(https://dart.dev/go/analysis-server-protocol)', | |
| }, | |
| help: 'Specify the protocol to use to communicate with the analysis server.', | |
| ); | |
| // This option is hidden but still accepted; it's effectively translated to | |
| // the 'protocol' option above. | |
| parser.addFlag( | |
| useLspFlag, | |
| negatable: false, | |
| help: 'Whether to use the Language Server Protocol (LSP).', | |
| hide: true, | |
| ); | |
| parser.addSeparator('Server diagnostics:'); | |
| parser.addOption( | |
| protocolTrafficLogOption, | |
| valueHelp: 'file path', | |
| help: 'Write server protocol traffic to the given file.', | |
| ); | |
| parser.addOption(protocolTrafficLogAliasOption, hide: true); | |
| parser.addOption( | |
| sessionLogOption, | |
| valueHelp: 'file path', | |
| help: 'Write a server session log to the given file.', | |
| ); | |
| parser.addOption( | |
| performanceLogOption, | |
| valueHelp: 'file path', | |
| help: 'Write a server performance log to the given file.', | |
| ); | |
| parser.addOption( | |
| analysisDriverLogOption, | |
| valueHelp: 'file path', | |
| help: 'Write analysis driver diagnostic data to the given file.', | |
| ); | |
| parser.addOption(analysisDriverLogAliasOption, hide: true); | |
| parser.addOption( | |
| diagnosticPortOption, | |
| valueHelp: 'port', | |
| help: | |
| 'Serve a web UI for status and performance data on the given ' | |
| 'port.', | |
| ); | |
| parser.addOption(diagnosticPortAliasOption, hide: true); | |
| // | |
| // Hidden; these have not yet been made public. | |
| // | |
| parser.addFlag( | |
| analyticsFlag, | |
| help: | |
| 'Allow or disallow sending analytics information to ' | |
| 'Google for this session.', | |
| hide: true, | |
| ); | |
| parser.addFlag( | |
| suppressAnalyticsFlag, | |
| negatable: false, | |
| help: 'Suppress analytics for this session.', | |
| hide: true, | |
| ); | |
| // | |
| // Hidden; these are for internal development. | |
| // | |
| parser.addOption( | |
| trainUsingOption, | |
| valueHelp: 'path', | |
| help: | |
| 'Pass in a directory to analyze for purposes of training an ' | |
| 'analysis server snapshot. Disables analytics.', | |
| hide: true, | |
| ); | |
| parser.addFlag( | |
| disableFileByteStoreOption, | |
| help: | |
| 'Disable use of (Evicting)FileByteStore. Intended for benchmarking.', | |
| hide: true, | |
| ); | |
| parser.addFlag( | |
| withFineDependenciesOption, | |
| help: 'Enable fine-grained dependencies.', | |
| defaultsTo: true, | |
| hide: true, | |
| ); | |
| parser.addFlag( | |
| disableServerExceptionHandlingOption, | |
| // TODO(jcollins-g): Pipeline option through and apply to all | |
| // exception-nullifying runZoned() calls. | |
| help: | |
| 'disable analyzer exception capture for interactive debugging ' | |
| 'of the server', | |
| hide: true, | |
| ); | |
| parser.addFlag( | |
| disableServerFeatureCompletionOption, | |
| help: 'disable all completion features', | |
| hide: true, | |
| ); | |
| parser.addFlag( | |
| disableServerFeatureSearchOption, | |
| help: 'disable all search features', | |
| hide: true, | |
| ); | |
| parser.addFlag( | |
| pluginsFlag, | |
| help: 'Use analyzer plugins', | |
| defaultsTo: true, | |
| hide: true, | |
| ); | |
| parser.addFlag( | |
| disableSilentAnalysisExceptionsOption, | |
| negatable: false, | |
| help: 'Prevent exceptions during analysis from being silent', | |
| hide: true, | |
| ); | |
| parser.addFlag( | |
| disableStatusNotificationDebouncingOption, | |
| negatable: false, | |
| help: 'Suppress debouncing of status notifications.', | |
| hide: true, | |
| ); | |
| parser.addFlag( | |
| internalPrintToConsoleOption, | |
| help: 'enable sending `print` output to the console', | |
| negatable: false, | |
| hide: true, | |
| ); | |
| parser.addOption( | |
| reportProtocolVersionOption, | |
| valueHelp: 'version', | |
| help: | |
| 'The protocol version that the server will report to the client, ' | |
| 'can be used to temporary enabling features that we expect to be ' | |
| 'available in future versions.', | |
| hide: true, | |
| ); | |
| // | |
| // Hidden; these are deprecated and no longer read from. | |
| // | |
| // Removed 11/15/2020. | |
| parser.addOption('completion-model', hide: true); | |
| // Removed 11/8/2020. | |
| parser.addFlag('dartpad', hide: true); | |
| // Removed 11/15/2020. | |
| parser.addFlag('enable-completion-model', hide: true); | |
| // Removed 9/23/2020. | |
| parser.addFlag('enable-instrumentation', hide: true); | |
| // Removed 11/12/2020. | |
| parser.addOption('file-read-mode', hide: true); | |
| // Removed 11/12/2020. | |
| parser.addFlag('ignore-unrecognized-flags', hide: true); | |
| // Removed 11/8/2020. | |
| parser.addFlag('preview-dart-2', hide: true); | |
| // Removed 11/12/2020. | |
| parser.addFlag('useAnalysisHighlight2', hide: true); | |
| // Removed 11/13/2020. | |
| parser.addFlag('use-new-relevance', hide: true); | |
| // Removed 9/23/2020. | |
| parser.addFlag('use-fasta-parser', hide: true); | |
| return parser; | |
| } | |
| /// Perform log files rolling. | |
| /// | |
| /// Rename existing files with names `[path].(x)` to `[path].(x+1)`. | |
| /// Keep at most [numOld] files. | |
| /// Rename the file with the given [path] to `[path].1`. | |
| static void _rollLogFiles(String path, int numOld) { | |
| for (var i = numOld - 1; i >= 0; i--) { | |
| try { | |
| var oldPath = i == 0 ? path : '$path.$i'; | |
| File(oldPath).renameSync('$path.${i + 1}'); | |
| } catch (e) { | |
| // If a file can't be renamed, then leave it and attempt to rename the | |
| // remaining files. | |
| } | |
| } | |
| } | |
| } | |
| /// Implements the [DiagnosticServer] class by wrapping an [HttpAnalysisServer]. | |
| class _DiagnosticServerImpl extends DiagnosticServer { | |
| late HttpAnalysisServer httpServer; | |
| @override | |
| Future<int> getServerPort() async => (await httpServer.serveHttp())!; | |
| Future<void> startOnPort(int port) { | |
| return httpServer.serveHttp(port); | |
| } | |
| } |