blob: 18b84383e059bc736fa2f3719ce1a867f7e0175d [file] [log] [blame]
// 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:ffi' as ffi;
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/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/completion/dart/completion_ranking.dart';
import 'package:analysis_server/src/services/completion/dart/uri_contributor.dart'
show UriContributor;
import 'package:analysis_server/src/socket_server.dart';
import 'package:analysis_server/src/utilities/request_statistics.dart';
import 'package:analysis_server/starter.dart';
import 'package:analyzer/exception/exception.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:args/args.dart';
import 'package:cli_util/cli_util.dart';
import 'package:linter/src/rules.dart' as linter;
import 'package:path/path.dart' as path;
import 'package:telemetry/crash_reporting.dart';
import 'package:telemetry/telemetry.dart' as telemetry;
/// Commandline argument parser. (Copied from analyzer/lib/options.dart)
/// TODO(pquitslund): replaces with a simple [ArgParser] instance
/// when the args package supports ignoring unrecognized
/// options/flags (https://github.com/dart-lang/args/issues/9).
/// TODO(devoncarew): Consider removing the ability to support unrecognized
/// flags for the analysis server.
class CommandLineParser {
final List<String> _knownFlags;
final ArgParser _parser;
/// Creates a new command line parser
CommandLineParser()
: _knownFlags = <String>[],
_parser = ArgParser(allowTrailingOptions: true);
ArgParser get parser => _parser;
/// Defines a flag.
/// See [ArgParser.addFlag()].
void addFlag(String name,
{String abbr,
String help,
bool defaultsTo = false,
bool negatable = true,
void Function(bool value) callback,
bool hide = false}) {
_knownFlags.add(name);
_parser.addFlag(name,
abbr: abbr,
help: help,
defaultsTo: defaultsTo,
negatable: negatable,
callback: callback,
hide: hide);
}
/// Defines an option that takes multiple values.
/// See [ArgParser.addMultiOption].
void addMultiOption(String name,
{String abbr,
String help,
String valueHelp,
Iterable<String> allowed,
Map<String, String> allowedHelp,
Iterable<String> defaultsTo,
void Function(List<String> values) callback,
bool splitCommas = true,
bool hide = false}) {
_knownFlags.add(name);
_parser.addMultiOption(name,
abbr: abbr,
help: help,
valueHelp: valueHelp,
allowed: allowed,
allowedHelp: allowedHelp,
defaultsTo: defaultsTo,
callback: callback,
splitCommas: splitCommas,
hide: hide);
}
/// Defines a value-taking option.
/// See [ArgParser.addOption()].
void addOption(String name,
{String abbr,
String help,
String valueHelp,
List<String> allowed,
Map<String, String> allowedHelp,
String defaultsTo,
void Function(Object) callback}) {
_knownFlags.add(name);
_parser.addOption(name,
abbr: abbr,
help: help,
valueHelp: valueHelp,
allowed: allowed,
allowedHelp: allowedHelp,
defaultsTo: defaultsTo,
callback: callback);
}
/// Generates a string displaying usage information for the defined options.
/// See [ArgParser.usage].
String getUsage() => _parser.usage;
/// Parses [args], a list of command-line arguments, matches them against the
/// flags and options defined by this parser, and returns the result. The
/// values of any defined variables are captured in the given map.
/// See [ArgParser].
ArgResults parse(List<String> args, Map<String, String> definedVariables) =>
_parser.parse(
_filterUnknowns(parseDefinedVariables(args, definedVariables)));
List<String> parseDefinedVariables(
List<String> args, Map<String, String> definedVariables) {
var count = args.length;
var remainingArgs = <String>[];
for (var i = 0; i < count; i++) {
var arg = args[i];
if (arg == '--') {
while (i < count) {
remainingArgs.add(args[i++]);
}
} else if (arg.startsWith('-D')) {
definedVariables[arg.substring(2)] = args[++i];
} else {
remainingArgs.add(arg);
}
}
return remainingArgs;
}
List<String> _filterUnknowns(List<String> args) {
// TODO(devoncarew): Consider dropping support for the
// --ignore-unrecognized-flags option.
// Only filter args if the ignore flag is specified.
if (args.contains('--ignore-unrecognized-flags')) {
// Filter all unrecognized flags and options.
var filtered = <String>[];
for (var i = 0; i < args.length; ++i) {
var arg = args[i];
if (arg.startsWith('--') && arg.length > 2) {
var option = arg.substring(2);
// remove any leading 'no-'
if (option.startsWith('no-')) {
option = option.substring(3);
}
// strip the last '=value'
var equalsOffset = option.lastIndexOf('=');
if (equalsOffset != -1) {
option = option.substring(0, equalsOffset);
}
// check the option
if (!_knownFlags.contains(option)) {
//"eat" params by advancing to the next flag/option
i = _getNextFlagIndex(args, i);
} else {
filtered.add(arg);
}
} else {
filtered.add(arg);
}
}
return filtered;
} else {
return args;
}
}
int _getNextFlagIndex(List<String> args, int i) {
for (; i < args.length; ++i) {
if (args[i].startsWith('--')) {
return i;
}
}
return i;
}
}
/// 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 BINARY_NAME = 'server';
/// The name of the option used to set the identifier for the client.
static const String CLIENT_ID = 'client-id';
/// The name of the option used to set the version for the client.
static const String CLIENT_VERSION = 'client-version';
/// The name of the option used to enable DartPad specific functionality.
static const String DARTPAD_OPTION = 'dartpad';
/// The name of the option used to disable exception handling.
static const String DISABLE_SERVER_EXCEPTION_HANDLING =
'disable-server-exception-handling';
/// The name of the option to disable the completion feature.
static const String DISABLE_SERVER_FEATURE_COMPLETION =
'disable-server-feature-completion';
/// The name of the option to disable the search feature.
static const String DISABLE_SERVER_FEATURE_SEARCH =
'disable-server-feature-search';
/// The name of the option used to enable instrumentation.
static const String ENABLE_INSTRUMENTATION_OPTION = 'enable-instrumentation';
/// The name of the option used to set the file read mode.
static const String FILE_READ_MODE = 'file-read-mode';
/// The name of the option used to print usage information.
static const String HELP_OPTION = 'help';
/// The name of the flag used to configure reporting analytics.
static const String ANALYTICS_FLAG = 'analytics';
/// Suppress analytics for this session.
static const String SUPPRESS_ANALYTICS_FLAG = 'suppress-analytics';
/// The name of the option used to cause instrumentation to also be written to
/// a local file.
static const String INSTRUMENTATION_LOG_FILE = 'instrumentation-log-file';
/// The name of the option used to specify if [print] should print to the
/// console instead of being intercepted.
static const String INTERNAL_PRINT_TO_CONSOLE = 'internal-print-to-console';
/// The name of the option used to describe the new analysis driver logger.
static const String NEW_ANALYSIS_DRIVER_LOG = 'new-analysis-driver-log';
/// The name of the flag used to enable version 2 of semantic highlight
/// notification.
static const String USE_ANALYSIS_HIGHLIGHT2 = 'useAnalysisHighlight2';
/// 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 PORT_OPTION = 'port';
/// The path to the SDK.
static const String SDK_OPTION = 'sdk';
/// The path to the data cache.
static const String CACHE_FOLDER = 'cache';
/// The name of the flag to use the Language Server Protocol (LSP).
static const String USE_LSP = 'lsp';
/// Whether or not to enable ML ranking for code completion.
static const String ENABLE_COMPLETION_MODEL = 'enable-completion-model';
/// The path on disk to a directory containing language model files for smart
/// code completion.
static const String COMPLETION_MODEL_FOLDER = 'completion-model';
/// A directory to analyze in order to train an analysis server snapshot.
static const String TRAIN_USING = 'train-using';
/// A flag indicating that the new code completion relevance computation
/// should be used to compute relevance scores.
static const String USE_NEW_RELEVANCE = 'use-new-relevance';
/// 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.
InstrumentationService instrumentationService;
HttpAnalysisServer httpServer;
Driver();
/// 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 [IsolateChannel].
@override
void start(List<String> arguments, [SendPort sendPort]) {
var parser = _createArgParser();
var results = parser.parse(arguments, <String, String>{});
var analysisServerOptions = AnalysisServerOptions();
analysisServerOptions.useAnalysisHighlight2 =
results[USE_ANALYSIS_HIGHLIGHT2];
analysisServerOptions.fileReadMode = results[FILE_READ_MODE];
analysisServerOptions.newAnalysisDriverLog =
results[NEW_ANALYSIS_DRIVER_LOG];
analysisServerOptions.clientId = results[CLIENT_ID];
analysisServerOptions.useLanguageServerProtocol = results[USE_LSP];
// For clients that don't supply their own identifier, use a default based on
// whether the server will run in LSP mode or not.
analysisServerOptions.clientId ??=
analysisServerOptions.useLanguageServerProtocol
? 'unknown.client.lsp'
: 'unknown.client.classic';
analysisServerOptions.clientVersion = results[CLIENT_VERSION];
analysisServerOptions.cacheFolder = results[CACHE_FOLDER];
analysisServerOptions.useNewRelevance = results[USE_NEW_RELEVANCE];
// Read in any per-SDK overrides specified in <sdk>/config/settings.json.
var sdkConfig = SdkConfiguration.readFromSdk();
analysisServerOptions.configurationOverrides = sdkConfig;
// ML model configuration.
// TODO(brianwilkerson) Uncomment the line below and delete the second line
// when there is a new completion model to query. Until then we ignore the
// flag to enable the model so that we can't try to read from a file that
// doesn't exist.
// final bool enableCompletionModel = results[ENABLE_COMPLETION_MODEL];
final enableCompletionModel = false;
analysisServerOptions.completionModelFolder =
results[COMPLETION_MODEL_FOLDER];
if (results.wasParsed(ENABLE_COMPLETION_MODEL) && !enableCompletionModel) {
// This is the case where the user has explicitly turned off model-based
// code completion.
analysisServerOptions.completionModelFolder = null;
}
// TODO(devoncarew): Simplify this logic and use the value from sdkConfig.
if (enableCompletionModel &&
analysisServerOptions.completionModelFolder == null) {
// The user has enabled ML code completion without explicitly setting a
// model for us to choose, so use the default one. We need to walk over
// from $SDK/bin/snapshots/analysis_server.dart.snapshot to
// $SDK/bin/model/lexeme.
analysisServerOptions.completionModelFolder = path.join(
File.fromUri(Platform.script).parent.path,
'..',
'model',
'lexeme',
);
}
// Analytics
bool disableAnalyticsForSession = results[SUPPRESS_ANALYTICS_FLAG];
if (results.wasParsed(TRAIN_USING)) {
disableAnalyticsForSession = true;
}
// Use sdkConfig to optionally override analytics settings.
final analyticsId = sdkConfig.analyticsId ?? 'UA-26406144-29';
final forceAnalyticsEnabled = sdkConfig.analyticsForceEnabled == true;
var analytics = telemetry.createAnalyticsInstance(
analyticsId,
'analysis-server',
disableForSession: disableAnalyticsForSession,
forceEnabled: forceAnalyticsEnabled,
);
analysisServerOptions.analytics = analytics;
// Record the client name as the application installer ID.
analytics.setSessionValue('aiid', analysisServerOptions.clientId);
if (analysisServerOptions.clientVersion != null) {
analytics.setSessionValue('cd1', analysisServerOptions.clientVersion);
}
var shouldSendCallback = () {
// Check sdkConfig to optionally force reporting on.
if (sdkConfig.crashReportingForceEnabled == true) {
return true;
}
// TODO(devoncarew): Replace with a real enablement check.
return false;
};
// Crash reporting
// Use sdkConfig to optionally override analytics settings.
final crashProductId = sdkConfig.crashReportingId ?? 'Dart_analysis_server';
final crashReportSender =
CrashReportSender.prod(crashProductId, shouldSendCallback);
// TODO(mfairhurst): send these to prod or disable.
final crashReportSenderAngular = CrashReportSender.staging(
'Dart_angular_analysis_plugin', shouldSendCallback);
if (telemetry.SHOW_ANALYTICS_UI) {
if (results.wasParsed(ANALYTICS_FLAG)) {
analytics.enabled = results[ANALYTICS_FLAG];
print(telemetry.createAnalyticsStatusMessage(analytics.enabled));
return null;
}
}
if (results[DARTPAD_OPTION]) {
UriContributor.suggestFilePaths = false;
}
{
bool disableCompletion = results[DISABLE_SERVER_FEATURE_COMPLETION];
bool disableSearch = results[DISABLE_SERVER_FEATURE_SEARCH];
if (disableCompletion || disableSearch) {
analysisServerOptions.featureSet = FeatureSet(
completion: !disableCompletion,
search: !disableSearch,
);
}
}
if (results[HELP_OPTION]) {
_printUsage(parser.parser, analytics, fromHelp: true);
return null;
}
final defaultSdkPath = _getSdkPath(results);
final dartSdkManager = DartSdkManager(defaultSdkPath);
// TODO(brianwilkerson) It would be nice to avoid creating an SDK that
// cannot 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);
//
// Initialize the instrumentation service.
//
String logFilePath = results[INSTRUMENTATION_LOG_FILE];
var allInstrumentationServices = instrumentationService == null
? <InstrumentationService>[]
: [instrumentationService];
if (logFilePath != null) {
_rollLogFiles(logFilePath, 5);
allInstrumentationServices.add(
InstrumentationLogAdapter(FileInstrumentationLogger(logFilePath)));
}
var errorNotifier = ErrorNotifier();
allInstrumentationServices.add(CrashReportingInstrumentation(
crashReportSender, crashReportSenderAngular));
instrumentationService =
MulticastInstrumentationService(allInstrumentationServices);
instrumentationService.logVersion(
results[TRAIN_USING] != null
? 'training-0'
: _readUuid(instrumentationService),
analysisServerOptions.clientId,
analysisServerOptions.clientVersion,
PROTOCOL_VERSION,
defaultSdk.languageVersion.toString(),
);
AnalysisEngine.instance.instrumentationService = instrumentationService;
int diagnosticServerPort;
if (results[PORT_OPTION] != null) {
try {
diagnosticServerPort = int.parse(results[PORT_OPTION]);
} on FormatException {
print('Invalid port number: ${results[PORT_OPTION]}');
print('');
_printUsage(parser.parser, analytics);
exitCode = 1;
return null;
}
}
if (analysisServerOptions.useLanguageServerProtocol) {
if (sendPort != null) {
throw UnimplementedError(
'Isolate usage not implemented for LspAnalysisServer');
}
startLspServer(results, analysisServerOptions, dartSdkManager,
instrumentationService, diagnosticServerPort, errorNotifier);
} else {
startAnalysisServer(
results,
analysisServerOptions,
parser,
dartSdkManager,
crashReportingAttachmentsBuilder,
instrumentationService,
RequestStatisticsHelper(),
analytics,
diagnosticServerPort,
errorNotifier,
sendPort);
}
}
void startAnalysisServer(
ArgResults results,
AnalysisServerOptions analysisServerOptions,
CommandLineParser parser,
DartSdkManager dartSdkManager,
CrashReportingAttachmentsBuilder crashReportingAttachmentsBuilder,
InstrumentationService instrumentationService,
RequestStatisticsHelper requestStatistics,
telemetry.Analytics analytics,
int diagnosticServerPort,
ErrorNotifier errorNotifier,
SendPort sendPort,
) {
var capture = results[DISABLE_SERVER_EXCEPTION_HANDLING]
? (_, Function f, {Function(String) print}) => f()
: _captureExceptions;
String trainDirectory = results[TRAIN_USING];
if (trainDirectory != null) {
if (!FileSystemEntity.isDirectorySync(trainDirectory)) {
print("Training directory '$trainDirectory' not found.\n");
exitCode = 1;
return null;
}
}
final serve_http = diagnosticServerPort != null;
//
// Register lint rules.
//
linter.registerLintRules();
var diagnosticServer = _DiagnosticServerImpl();
// Ping analytics with our initial call.
analytics.sendScreenView('home');
//
// Create the sockets and start listening for requests.
//
final socketServer = SocketServer(
analysisServerOptions,
dartSdkManager,
crashReportingAttachmentsBuilder,
instrumentationService,
requestStatistics,
diagnosticServer,
detachableFileSystemManager);
httpServer = HttpAnalysisServer(socketServer);
errorNotifier.server = socketServer.analysisServer;
diagnosticServer.httpServer = httpServer;
if (serve_http) {
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;
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);
if (serve_http) {
httpServer.close();
}
await instrumentationService.shutdown();
socketServer.analysisServer.shutdown();
try {
tempDriverDir.deleteSync(recursive: true);
} catch (_) {
// ignore any exception
}
exit(exitCode);
}();
} else {
capture(instrumentationService, () {
Future serveResult;
if (sendPort == null) {
var stdioServer = StdioAnalysisServer(socketServer);
serveResult = stdioServer.serveStdio();
} else {
var isolateAnalysisServer = IsolateAnalysisServer(socketServer);
serveResult = isolateAnalysisServer.serveIsolate(sendPort);
}
serveResult.then((_) async {
if (serve_http) {
httpServer.close();
}
await instrumentationService.shutdown();
socketServer.analysisServer.shutdown();
if (sendPort == null) exit(0);
});
startCompletionRanking(socketServer, null, analysisServerOptions);
},
print: results[INTERNAL_PRINT_TO_CONSOLE]
? null
: httpServer.recordPrint);
}
}
/// This will be invoked after createAnalysisServer has been called on the
/// socket server. At that point, we'll be able to send a server.error
/// notification in case model startup fails.
void startCompletionRanking(
SocketServer socketServer,
LspSocketServer lspSocketServer,
AnalysisServerOptions analysisServerOptions) {
// If ML completion is not enabled, or we're on a 32-bit machine, don't try
// and start the completion model.
if (analysisServerOptions.completionModelFolder == null ||
ffi.sizeOf<ffi.IntPtr>() == 4) {
return;
}
// Start completion model isolate if this is a 64 bit system and analysis
// server was configured to load a language model on disk.
CompletionRanking.instance =
CompletionRanking(analysisServerOptions.completionModelFolder);
CompletionRanking.instance.start().catchError((exception, stackTrace) {
// Disable smart ranking if model startup fails.
analysisServerOptions.completionModelFolder = null;
// TODO(brianwilkerson) Shutdown the isolates that have already been
// started.
CompletionRanking.instance = null;
AnalysisEngine.instance.instrumentationService.logException(
CaughtException.withMessage(
'Failed to start ranking model isolate', exception, stackTrace));
});
}
void startLspServer(
ArgResults args,
AnalysisServerOptions analysisServerOptions,
DartSdkManager dartSdkManager,
InstrumentationService instrumentationService,
int diagnosticServerPort,
ErrorNotifier errorNotifier,
) {
var capture = args[DISABLE_SERVER_EXCEPTION_HANDLING]
? (_, Function f, {Function(String) print}) => f()
: _captureExceptions;
final serve_http = diagnosticServerPort != null;
linter.registerLintRules();
var diagnosticServer = _DiagnosticServerImpl();
final socketServer = LspSocketServer(
analysisServerOptions,
diagnosticServer,
dartSdkManager,
instrumentationService,
);
errorNotifier.server = socketServer.analysisServer;
httpServer = HttpAnalysisServer(socketServer);
diagnosticServer.httpServer = httpServer;
if (serve_http) {
diagnosticServer.startOnPort(diagnosticServerPort);
}
capture(instrumentationService, () {
var stdioServer = LspStdioAnalysisServer(socketServer);
stdioServer.serveStdio().then((_) async {
// Only shutdown the server and exit if the server is not already
// handling the shutdown.
if (!socketServer.analysisServer.willExit) {
socketServer.analysisServer.shutdown();
exit(0);
}
});
startCompletionRanking(null, socketServer, analysisServerOptions);
});
}
/// 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, void Function() callback,
{void Function(String line) print}) {
void errorFunction(Zone self, ZoneDelegate parent, Zone zone,
dynamic exception, StackTrace stackTrace) {
service.logException(exception, 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 and return the parser used to parse the command-line arguments.
CommandLineParser _createArgParser() {
var parser = CommandLineParser();
parser.addFlag(HELP_OPTION,
help: 'print this help message without starting a server',
abbr: 'h',
defaultsTo: false,
negatable: false);
parser.addOption(CLIENT_ID,
valueHelp: 'name', help: 'an identifier used to identify the client');
parser.addOption(CLIENT_VERSION,
valueHelp: 'version', help: 'the version of the client');
parser.addFlag(DARTPAD_OPTION,
help: 'enable DartPad specific functionality',
defaultsTo: false,
hide: true);
parser.addFlag(DISABLE_SERVER_EXCEPTION_HANDLING,
// 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',
defaultsTo: false,
hide: true);
parser.addFlag(DISABLE_SERVER_FEATURE_COMPLETION,
help: 'disable all completion features', defaultsTo: false, hide: true);
parser.addFlag(DISABLE_SERVER_FEATURE_SEARCH,
help: 'disable all search features', defaultsTo: false, hide: true);
parser.addFlag(ENABLE_INSTRUMENTATION_OPTION,
help: 'enable sending instrumentation information to a server',
defaultsTo: false,
negatable: false);
parser.addOption(INSTRUMENTATION_LOG_FILE,
valueHelp: 'file path',
help: 'write instrumentation data to the given file');
parser.addFlag(INTERNAL_PRINT_TO_CONSOLE,
help: 'enable sending `print` output to the console',
defaultsTo: false,
negatable: false);
parser.addOption(NEW_ANALYSIS_DRIVER_LOG,
valueHelp: 'path',
help: "set a destination for the new analysis driver's log");
parser.addFlag(ANALYTICS_FLAG,
help: 'enable or disable sending analytics information to Google',
hide: !telemetry.SHOW_ANALYTICS_UI);
parser.addFlag(SUPPRESS_ANALYTICS_FLAG,
negatable: false,
help: 'suppress analytics for this session',
hide: !telemetry.SHOW_ANALYTICS_UI);
parser.addOption(PORT_OPTION,
valueHelp: 'port',
help: 'the http diagnostic port on which the server provides'
' status and performance information');
parser.addOption(SDK_OPTION,
valueHelp: 'path', help: 'Path to the Dart sdk');
parser.addFlag(USE_ANALYSIS_HIGHLIGHT2,
help: 'enable version 2 of semantic highlight',
defaultsTo: false,
negatable: false);
parser.addOption(FILE_READ_MODE,
help: 'an option for reading files (some clients normalize eol '
'characters, which make the file offset and range information '
'incorrect)',
valueHelp: 'mode',
allowed: ['as-is', 'normalize-eol-always'],
allowedHelp: {
'as-is': 'file contents are read as-is',
'normalize-eol-always':
r"eol characters normalized to the single character new line ('\n')"
},
defaultsTo: 'as-is');
parser.addOption(CACHE_FOLDER,
valueHelp: 'path', help: 'Path to the location to write cache data');
parser.addFlag(USE_LSP,
defaultsTo: false,
negatable: false,
help: 'Whether to use the Language Server Protocol');
parser.addFlag(ENABLE_COMPLETION_MODEL,
help: 'Whether or not to turn on ML ranking for code completion');
parser.addOption(COMPLETION_MODEL_FOLDER,
valueHelp: 'path',
help: 'Path to the location of a code completion model');
parser.addOption(TRAIN_USING,
valueHelp: 'path',
help: 'Pass in a directory to analyze for purposes of training an '
'analysis server snapshot.');
//
// Temporary flags.
//
parser.addFlag(USE_NEW_RELEVANCE,
defaultsTo: true,
help: 'Use the new relevance computation for code completion.');
//
// Deprecated options - no longer read from.
//
parser.addFlag('use-fasta-parser', defaultsTo: true, hide: true);
parser.addFlag('preview-dart-2', hide: true);
return parser;
}
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';
}
String _getSdkPath(ArgResults args) {
if (args[SDK_OPTION] != null) {
return args[SDK_OPTION];
} else {
return getSdkPath();
}
}
/// Print information about how to use the server.
void _printUsage(ArgParser parser, telemetry.Analytics analytics,
{bool fromHelp = false}) {
print('Usage: $BINARY_NAME [flags]');
print('');
print('Supported flags are:');
print(parser.usage);
if (telemetry.SHOW_ANALYTICS_UI) {
// Print analytics status and information.
if (fromHelp) {
print('');
print(telemetry.analyticsNotice);
}
print('');
print(telemetry.createAnalyticsStatusMessage(analytics.enabled,
command: ANALYTICS_FLAG));
}
}
/// Read the UUID from disk, generating and storing a new one if necessary.
String _readUuid(InstrumentationService service) {
final 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 != null && uuid.length > 5) {
return uuid;
}
}
} catch (exception, stackTrace) {
service.logException(exception, stackTrace);
}
var uuid = _generateUuidString();
try {
uuidFile.parent.createSync(recursive: true);
uuidFile.writeAsStringSync(uuid);
} catch (exception, stackTrace) {
service.logException(exception, stackTrace);
// Slightly alter the uuid to indicate it was not persisted
uuid = 'temp-$uuid';
}
return uuid;
}
/// 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 {
HttpAnalysisServer httpServer;
_DiagnosticServerImpl();
@override
Future<int> getServerPort() => httpServer.serveHttp();
Future startOnPort(int port) {
return httpServer.serveHttp(port);
}
}