Version 2.14.0-319.0.dev
Merge commit 'a99b92c2525c6e490e2a55fce2888d251688b67a' into 'dev'
diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json
index 204df98..3174dac 100644
--- a/.dart_tool/package_config.json
+++ b/.dart_tool/package_config.json
@@ -11,7 +11,7 @@
"constraint, update this by running tools/generate_package_config.dart."
],
"configVersion": 2,
- "generated": "2021-07-02T15:48:57.033703",
+ "generated": "2021-07-14T10:43:41.119864",
"generator": "tools/generate_package_config.dart",
"packages": [
{
diff --git a/DEPS b/DEPS
index 44d5d82..bbcf4ae 100644
--- a/DEPS
+++ b/DEPS
@@ -164,7 +164,7 @@
"test_process_tag": "2.0.0",
"term_glyph_rev": "6a0f9b6fb645ba75e7a00a4e20072678327a0347",
"test_reflective_loader_rev": "54e930a11c372683792e22bddad79197728c91ce",
- "test_rev": "030816c32b6fe78d5fb7653afbd9f63cca18bacf",
+ "test_rev": "099dcc4d052a30c6921489cfbefa1c8531d12975",
"typed_data_tag": "f94fc57b8e8c0e4fe4ff6cfd8290b94af52d3719",
"usage_rev": "e0780cd8b2f8af69a28dc52678ffe8492da27d06",
"vector_math_rev": "0c9f5d68c047813a6dcdeb88ba7a42daddf25025",
diff --git a/pkg/analyzer/lib/src/context/builder.dart b/pkg/analyzer/lib/src/context/builder.dart
index 502ea26..ec0aaa7 100644
--- a/pkg/analyzer/lib/src/context/builder.dart
+++ b/pkg/analyzer/lib/src/context/builder.dart
@@ -8,25 +8,6 @@
import 'package:analyzer/file_system/file_system.dart';
import 'package:yaml/yaml.dart';
-/// TODO(scheglov) It is not used, inline it.
-class ContextBuilderOptions {
- /// The file path of the analysis options file that should be used in place of
- /// any file in the root directory or a parent of the root directory, or `null`
- /// if the normal lookup mechanism should be used.
- String? defaultAnalysisOptionsFilePath;
-
- /// A table mapping variable names to values for the declared variables.
- Map<String, String> declaredVariables = {};
-
- /// The file path of the .packages file that should be used in place of any
- /// file found using the normal (Package Specification DEP) lookup mechanism,
- /// or `null` if the normal lookup mechanism should be used.
- String? defaultPackageFilePath;
-
- /// Initialize a newly created set of options
- ContextBuilderOptions();
-}
-
/// Given a package map, check in each package's lib directory for the existence
/// of an `_embedder.yaml` file. If the file contains a top level YamlMap, it
/// will be added to the [embedderYamls] map.
diff --git a/pkg/analyzer_cli/lib/src/driver.dart b/pkg/analyzer_cli/lib/src/driver.dart
index 5b0948e..a3b5448 100644
--- a/pkg/analyzer_cli/lib/src/driver.dart
+++ b/pkg/analyzer_cli/lib/src/driver.dart
@@ -431,7 +431,7 @@
}
void _verifyAnalysisOptionsFileExists(CommandLineOptions options) {
- var path = options.analysisOptionsFile;
+ var path = options.defaultAnalysisOptionsPath;
if (path != null) {
if (!resourceProvider.getFile(path).exists) {
printAndFail('Options file not found: $path',
@@ -449,8 +449,8 @@
CommandLineOptions previous, CommandLineOptions newOptions) {
return previous != null &&
newOptions != null &&
- newOptions.packageConfigPath == previous.packageConfigPath &&
- _equalMaps(newOptions.definedVariables, previous.definedVariables) &&
+ newOptions.defaultPackagesPath == previous.defaultPackagesPath &&
+ _equalMaps(newOptions.declaredVariables, previous.declaredVariables) &&
newOptions.log == previous.log &&
newOptions.disableHints == previous.disableHints &&
newOptions.showPackageWarnings == previous.showPackageWarnings &&
@@ -554,8 +554,8 @@
_collection = AnalysisContextCollectionImpl(
byteStore: Driver.analysisDriverMemoryByteStore,
includedPaths: _pathList,
- optionsFile: _commandLineOptions.analysisOptionsFile,
- packagesFile: _commandLineOptions.packageConfigPath,
+ optionsFile: _commandLineOptions.defaultAnalysisOptionsPath,
+ packagesFile: _commandLineOptions.defaultPackagesPath,
resourceProvider: _resourceProvider,
sdkPath: _commandLineOptions.dartSdkPath,
updateAnalysisOptions: _updateAnalysisOptions,
diff --git a/pkg/analyzer_cli/lib/src/options.dart b/pkg/analyzer_cli/lib/src/options.dart
index bb2da44..7ec27d6 100644
--- a/pkg/analyzer_cli/lib/src/options.dart
+++ b/pkg/analyzer_cli/lib/src/options.dart
@@ -6,7 +6,6 @@
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/file_system/file_system.dart';
-import 'package:analyzer/src/context/builder.dart';
import 'package:analyzer/src/dart/analysis/experiments.dart';
import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl;
import 'package:analyzer/src/util/file_paths.dart' as file_paths;
@@ -51,8 +50,18 @@
class CommandLineOptions {
final ArgResults _argResults;
- /// The options defining the context in which analysis is performed.
- final ContextBuilderOptions contextBuilderOptions;
+ /// The file path of the analysis options file that should be used in place of
+ /// any file in the root directory or a parent of the root directory,
+ /// or `null` if the normal lookup mechanism should be used.
+ String defaultAnalysisOptionsPath;
+
+ /// The file path of the .packages file that should be used in place of any
+ /// file found using the normal (Package Specification DEP) lookup mechanism,
+ /// or `null` if the normal lookup mechanism should be used.
+ String defaultPackagesPath;
+
+ /// A table mapping variable names to values for the declared variables.
+ final Map<String, String> declaredVariables = {};
/// The path to the dart SDK.
String dartSdkPath;
@@ -124,10 +133,6 @@
ResourceProvider resourceProvider,
ArgResults args,
) : _argResults = args,
- contextBuilderOptions = _createContextBuilderOptions(
- resourceProvider,
- args,
- ),
dartSdkPath = cast(args[_sdkPathOption]),
disableCacheFlushing = cast(args['disable-cache-flushing']),
disableHints = cast(args['no-hints']),
@@ -149,11 +154,41 @@
lintsAreFatal = cast(args['fatal-lints']),
trainSnapshot = cast(args['train-snapshot']),
verbose = cast(args['verbose']),
- color = cast(args['color']);
+ color = cast(args['color']) {
+ //
+ // File locations.
+ //
+ defaultAnalysisOptionsPath = _absoluteNormalizedPath(
+ resourceProvider,
+ cast(args[_analysisOptionsFileOption]),
+ );
+ defaultPackagesPath = _absoluteNormalizedPath(
+ resourceProvider,
+ cast(args[_packagesOption]),
+ );
- /// The path to an analysis options file
- String get analysisOptionsFile =>
- contextBuilderOptions.defaultAnalysisOptionsFilePath;
+ //
+ // Declared variables.
+ //
+ var variables = (args[_defineVariableOption] as List).cast<String>();
+ for (var variable in variables) {
+ var index = variable.indexOf('=');
+ if (index < 0) {
+ // TODO (brianwilkerson) Decide the semantics we want in this case.
+ // The VM prints "No value given to -D option", then tries to load '-Dfoo'
+ // as a file and dies. Unless there was nothing after the '-D', in which
+ // case it prints the warning and ignores the option.
+ } else {
+ var name = variable.substring(0, index);
+ if (name.isNotEmpty) {
+ // TODO (brianwilkerson) Decide the semantics we want in the case where
+ // there is no name. If there is no name, the VM tries to load a file
+ // named '-D' and dies.
+ declaredVariables[name] = variable.substring(index + 1);
+ }
+ }
+ }
+ }
/// The default language version for files that are not in a package.
/// (Or null if no default language version to force.)
@@ -161,10 +196,6 @@
return cast(_argResults[_defaultLanguageVersionOption]);
}
- /// A table mapping the names of defined variables to their values.
- Map<String, String> get definedVariables =>
- contextBuilderOptions.declaredVariables;
-
/// A list of the names of the experiments that are to be enabled.
List<String> get enabledExperiments {
return cast(_argResults[_enableExperimentOption]);
@@ -176,9 +207,6 @@
bool get noImplicitDynamic => _argResults[_noImplicitDynamicFlag] as bool;
- /// The path to a `.packages` configuration file
- String get packageConfigPath => contextBuilderOptions.defaultPackageFilePath;
-
/// Update the [analysisOptions] with flags that the user specified
/// explicitly. The [analysisOptions] are usually loaded from one of
/// `analysis_options.yaml` files, possibly with includes. We consider
@@ -307,57 +335,17 @@
return options;
}
- /// Use the command-line [args] to create a context builder options.
- static ContextBuilderOptions _createContextBuilderOptions(
+ static String _absoluteNormalizedPath(
ResourceProvider resourceProvider,
- ArgResults args,
+ String path,
) {
- String absoluteNormalizedPath(String path) {
- if (path == null) {
- return null;
- }
- var pathContext = resourceProvider.pathContext;
- return pathContext.normalize(
- pathContext.absolute(path),
- );
+ if (path == null) {
+ return null;
}
-
- var builderOptions = ContextBuilderOptions();
-
- //
- // File locations.
- //
- builderOptions.defaultAnalysisOptionsFilePath = absoluteNormalizedPath(
- cast(args[_analysisOptionsFileOption]),
+ var pathContext = resourceProvider.pathContext;
+ return pathContext.normalize(
+ pathContext.absolute(path),
);
- builderOptions.defaultPackageFilePath = absoluteNormalizedPath(
- cast(args[_packagesOption]),
- );
- //
- // Declared variables.
- //
- var declaredVariables = <String, String>{};
- var variables = (args[_defineVariableOption] as List).cast<String>();
- for (var variable in variables) {
- var index = variable.indexOf('=');
- if (index < 0) {
- // TODO (brianwilkerson) Decide the semantics we want in this case.
- // The VM prints "No value given to -D option", then tries to load '-Dfoo'
- // as a file and dies. Unless there was nothing after the '-D', in which
- // case it prints the warning and ignores the option.
- } else {
- var name = variable.substring(0, index);
- if (name.isNotEmpty) {
- // TODO (brianwilkerson) Decide the semantics we want in the case where
- // there is no name. If there is no name, the VM tries to load a file
- // named '-D' and dies.
- declaredVariables[name] = variable.substring(index + 1);
- }
- }
- }
- builderOptions.declaredVariables = declaredVariables;
-
- return builderOptions;
}
/// Add the standard flags and options to the given [parser]. The standard flags
diff --git a/pkg/analyzer_cli/lib/src/perf_report.dart b/pkg/analyzer_cli/lib/src/perf_report.dart
index 25c21c5..04caa35 100644
--- a/pkg/analyzer_cli/lib/src/perf_report.dart
+++ b/pkg/analyzer_cli/lib/src/perf_report.dart
@@ -38,8 +38,8 @@
'showPackageWarnings': options.showPackageWarnings,
'showPackageWarningsPrefix': options.showPackageWarningsPrefix,
'showSdkWarnings': options.showSdkWarnings,
- 'definedVariables': options.definedVariables,
- 'packageConfigPath': options.packageConfigPath,
+ 'definedVariables': options.declaredVariables,
+ 'packageConfigPath': options.defaultPackagesPath,
'sourceFiles': options.sourceFiles,
};
diff --git a/pkg/analyzer_cli/test/options_test.dart b/pkg/analyzer_cli/test/options_test.dart
index 8d3a0fa..4b7167b 100644
--- a/pkg/analyzer_cli/test/options_test.dart
+++ b/pkg/analyzer_cli/test/options_test.dart
@@ -84,8 +84,8 @@
test('defined variables', () {
var options = parse(['--dart-sdk', '.', '-Dfoo=bar', 'foo.dart']);
- expect(options.definedVariables['foo'], equals('bar'));
- expect(options.definedVariables['bar'], isNull);
+ expect(options.declaredVariables['foo'], equals('bar'));
+ expect(options.declaredVariables['bar'], isNull);
});
test('disable cache flushing', () {
@@ -210,7 +210,7 @@
test('options', () {
var options =
parse(['--dart-sdk', '.', '--options', 'options.yaml', 'foo.dart']);
- expect(options.analysisOptionsFile, endsWith('options.yaml'));
+ expect(options.defaultAnalysisOptionsPath, endsWith('options.yaml'));
});
test('lints', () {
@@ -284,8 +284,7 @@
void test_declaredVariables() {
_parse(['-Da=0', '-Db=', 'a.dart']);
- var options = commandLineOptions.contextBuilderOptions;
- var definedVariables = options.declaredVariables;
+ var definedVariables = commandLineOptions.declaredVariables;
expect(definedVariables['a'], '0');
expect(definedVariables['b'], '');
@@ -296,9 +295,8 @@
var expected = 'my_options.yaml';
_parse(['--options=$expected', 'a.dart']);
- var builderOptions = commandLineOptions.contextBuilderOptions;
expect(
- builderOptions.defaultAnalysisOptionsFilePath,
+ commandLineOptions.defaultAnalysisOptionsPath,
endsWith(expected),
);
}
@@ -307,20 +305,17 @@
var expected = 'my_package_config.json';
_parse(['--packages=$expected', 'a.dart']);
- var builderOptions = commandLineOptions.contextBuilderOptions;
expect(
- builderOptions.defaultPackageFilePath,
+ commandLineOptions.defaultPackagesPath,
endsWith(expected),
);
}
void test_defaults() {
_parse(['a.dart']);
- var builderOptions = commandLineOptions.contextBuilderOptions;
- expect(builderOptions, isNotNull);
- expect(builderOptions.declaredVariables, isEmpty);
- expect(builderOptions.defaultAnalysisOptionsFilePath, isNull);
- expect(builderOptions.defaultPackageFilePath, isNull);
+ expect(commandLineOptions.declaredVariables, isEmpty);
+ expect(commandLineOptions.defaultAnalysisOptionsPath, isNull);
+ expect(commandLineOptions.defaultPackagesPath, isNull);
}
void test_filterUnknownArguments() {
diff --git a/pkg/dds/CHANGELOG.md b/pkg/dds/CHANGELOG.md
index 84e2e6a..9859145 100644
--- a/pkg/dds/CHANGELOG.md
+++ b/pkg/dds/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 2.0.2
+- Fix possibility of `LateInitializationError` being thrown when trying to
+ cleanup after an error during initialization.
+
# 2.0.1
- Update `package:vm_service` to ^7.0.0.
diff --git a/pkg/dds/lib/src/dds_impl.dart b/pkg/dds/lib/src/dds_impl.dart
index c65ba5d..4360bcc 100644
--- a/pkg/dds/lib/src/dds_impl.dart
+++ b/pkg/dds/lib/src/dds_impl.dart
@@ -67,7 +67,7 @@
Future<void> startService() async {
bool started = false;
- final completer = Completer<void>();
+ DartDevelopmentServiceException? error;
// TODO(bkonyi): throw if we've already shutdown.
// Establish the connection to the VM service.
_vmServiceSocket = webSocketBuilder(remoteVmServiceWsUri);
@@ -76,18 +76,25 @@
unawaited(
vmServiceClient.listen().then(
(_) {
- shutdown();
- if (!started && !completer.isCompleted) {
- completer
- .completeError(DartDevelopmentServiceException.failedToStart());
+ if (started) {
+ shutdown();
+ } else {
+ // If we fail to connect to the service or the connection is
+ // terminated while we're starting up, we'll need to cleanup later
+ // once DDS has finished initializing to make sure all ports are
+ // closed before throwing the exception.
+ error = DartDevelopmentServiceException.failedToStart();
}
},
onError: (e, st) {
- shutdown();
- if (!completer.isCompleted) {
- completer.completeError(
- DartDevelopmentServiceException.connectionIssue(e.toString()),
- st,
+ if (started) {
+ shutdown();
+ } else {
+ // If we encounter an error while we're starting up, we'll need to
+ // cleanup later once DDS has finished initializing to make sure
+ // all ports are closed before throwing the exception.
+ error = DartDevelopmentServiceException.connectionIssue(
+ e.toString(),
);
}
},
@@ -104,13 +111,15 @@
// Once we have a connection to the VM service, we're ready to spawn the intermediary.
await _startDDSServer();
started = true;
- completer.complete();
} on StateError {
/* Ignore json-rpc state errors */
- } catch (e, st) {
- completer.completeError(e, st);
}
- return completer.future;
+
+ // Check if we encountered any errors during startup, cleanup, and throw.
+ if (error != null) {
+ await shutdown();
+ throw error!;
+ }
}
Future<void> _startDDSServer() async {
diff --git a/pkg/dds/lib/src/devtools/devtools_client.dart b/pkg/dds/lib/src/devtools/devtools_client.dart
index e5ec54e..8bc3564 100644
--- a/pkg/dds/lib/src/devtools/devtools_client.dart
+++ b/pkg/dds/lib/src/devtools/devtools_client.dart
@@ -4,11 +4,12 @@
import 'dart:async';
-import 'package:devtools_shared/devtools_server.dart';
import 'package:json_rpc_2/src/server.dart' as json_rpc;
import 'package:sse/src/server/sse_handler.dart';
import 'package:stream_channel/stream_channel.dart';
+import 'server_api.dart';
+
class LoggingMiddlewareSink<S> implements StreamSink<S> {
LoggingMiddlewareSink(this.sink);
diff --git a/pkg/dds/lib/src/devtools/devtools_handler.dart b/pkg/dds/lib/src/devtools/devtools_handler.dart
index 3b55bfb..87c4fa6 100644
--- a/pkg/dds/lib/src/devtools/devtools_handler.dart
+++ b/pkg/dds/lib/src/devtools/devtools_handler.dart
@@ -5,13 +5,13 @@
import 'dart:async';
import 'package:dds/src/constants.dart';
-import 'package:devtools_shared/devtools_server.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf_static/shelf_static.dart';
import 'package:sse/server/sse_handler.dart';
import '../dds_impl.dart';
import 'devtools_client.dart';
+import 'server_api.dart';
/// Returns a [Handler] which handles serving DevTools and the DevTools server
/// API under $DDS_URI/devtools/.
diff --git a/pkg/dds/lib/src/devtools/file_system.dart b/pkg/dds/lib/src/devtools/file_system.dart
new file mode 100644
index 0000000..d5d6c6a
--- /dev/null
+++ b/pkg/dds/lib/src/devtools/file_system.dart
@@ -0,0 +1,82 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// TODO(bkonyi): remove once package:devtools_server_api is available
+// See https://github.com/flutter/devtools/issues/2958.
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:path/path.dart' as path;
+
+import 'usage.dart';
+
+class LocalFileSystem {
+ static String _userHomeDir() {
+ final String envKey =
+ Platform.operatingSystem == 'windows' ? 'APPDATA' : 'HOME';
+ final String? value = Platform.environment[envKey];
+ return value == null ? '.' : value;
+ }
+
+ /// Returns the path to the DevTools storage directory.
+ static String devToolsDir() {
+ return path.join(_userHomeDir(), '.flutter-devtools');
+ }
+
+ /// Moves the .devtools file to ~/.flutter-devtools/.devtools if the .devtools file
+ /// exists in the user's home directory.
+ static void maybeMoveLegacyDevToolsStore() {
+ final file = File(path.join(_userHomeDir(), DevToolsUsage.storeName));
+ if (file.existsSync()) {
+ ensureDevToolsDirectory();
+ file.copySync(path.join(devToolsDir(), DevToolsUsage.storeName));
+ file.deleteSync();
+ }
+ }
+
+ /// Creates the ~/.flutter-devtools directory if it does not already exist.
+ static void ensureDevToolsDirectory() {
+ Directory('${LocalFileSystem.devToolsDir()}').createSync();
+ }
+
+ /// Returns a DevTools file from the given path.
+ ///
+ /// Only files within ~/.flutter-devtools/ can be accessed.
+ static File? devToolsFileFromPath(String pathFromDevToolsDir) {
+ if (pathFromDevToolsDir.contains('..')) {
+ // The passed in path should not be able to walk up the directory tree
+ // outside of the ~/.flutter-devtools/ directory.
+ return null;
+ }
+ ensureDevToolsDirectory();
+ final file = File(path.join(devToolsDir(), pathFromDevToolsDir));
+ if (!file.existsSync()) {
+ return null;
+ }
+ return file;
+ }
+
+ /// Returns a DevTools file from the given path as encoded json.
+ ///
+ /// Only files within ~/.flutter-devtools/ can be accessed.
+ static String? devToolsFileAsJson(String pathFromDevToolsDir) {
+ final file = devToolsFileFromPath(pathFromDevToolsDir);
+ if (file == null) return null;
+
+ final fileName = path.basename(file.path);
+ if (!fileName.endsWith('.json')) return null;
+
+ final content = file.readAsStringSync();
+ final json = jsonDecode(content);
+ json['lastModifiedTime'] = file.lastModifiedSync().toString();
+ return jsonEncode(json);
+ }
+
+ /// Whether the flutter store file exists.
+ static bool flutterStoreExists() {
+ final flutterStore = File('${_userHomeDir()}/.flutter');
+ return flutterStore.existsSync();
+ }
+}
diff --git a/pkg/dds/lib/src/devtools/server_api.dart b/pkg/dds/lib/src/devtools/server_api.dart
new file mode 100644
index 0000000..5f21a03
--- /dev/null
+++ b/pkg/dds/lib/src/devtools/server_api.dart
@@ -0,0 +1,228 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// TODO(bkonyi): remove once package:devtools_server_api is available
+// See https://github.com/flutter/devtools/issues/2958.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:devtools_shared/devtools_shared.dart';
+import 'package:shelf/shelf.dart' as shelf;
+
+import 'file_system.dart';
+import 'usage.dart';
+
+/// The DevTools server API.
+///
+/// This defines endpoints that serve all requests that come in over api/.
+class ServerApi {
+ static const errorNoActiveSurvey = 'ERROR: setActiveSurvey not called.';
+
+ /// Determines whether or not [request] is an API call.
+ static bool canHandle(shelf.Request request) {
+ return request.url.path.startsWith(apiPrefix);
+ }
+
+ /// Handles all requests.
+ ///
+ /// To override an API call, pass in a subclass of [ServerApi].
+ static FutureOr<shelf.Response> handle(
+ shelf.Request request, [
+ ServerApi? api,
+ ]) {
+ api ??= ServerApi();
+ switch (request.url.path) {
+ // ----- Flutter Tool GA store. -----
+ case apiGetFlutterGAEnabled:
+ // Is Analytics collection enabled?
+ return api.getCompleted(
+ request,
+ json.encode(FlutterUsage.doesStoreExist ? _usage!.enabled : null),
+ );
+ case apiGetFlutterGAClientId:
+ // Flutter Tool GA clientId - ONLY get Flutter's clientId if enabled is
+ // true.
+ return (FlutterUsage.doesStoreExist)
+ ? api.getCompleted(
+ request,
+ json.encode(_usage!.enabled ? _usage!.clientId : null),
+ )
+ : api.getCompleted(
+ request,
+ json.encode(null),
+ );
+
+ // ----- DevTools GA store. -----
+
+ case apiResetDevTools:
+ _devToolsUsage.reset();
+ return api.getCompleted(request, json.encode(true));
+ case apiGetDevToolsFirstRun:
+ // Has DevTools been run first time? To bring up welcome screen.
+ return api.getCompleted(
+ request,
+ json.encode(_devToolsUsage.isFirstRun),
+ );
+ case apiGetDevToolsEnabled:
+ // Is DevTools Analytics collection enabled?
+ return api.getCompleted(request, json.encode(_devToolsUsage.enabled));
+ case apiSetDevToolsEnabled:
+ // Enable or disable DevTools analytics collection.
+ final queryParams = request.requestedUri.queryParameters;
+ if (queryParams.containsKey(devToolsEnabledPropertyName)) {
+ _devToolsUsage.enabled =
+ json.decode(queryParams[devToolsEnabledPropertyName]!);
+ }
+ return api.setCompleted(request, json.encode(_devToolsUsage.enabled));
+
+ // ----- DevTools survey store. -----
+
+ case apiSetActiveSurvey:
+ // Assume failure.
+ bool result = false;
+
+ // Set the active survey used to store subsequent apiGetSurveyActionTaken,
+ // apiSetSurveyActionTaken, apiGetSurveyShownCount, and
+ // apiIncrementSurveyShownCount calls.
+ final queryParams = request.requestedUri.queryParameters;
+ if (queryParams.keys.length == 1 &&
+ queryParams.containsKey(activeSurveyName)) {
+ final String theSurveyName = queryParams[activeSurveyName]!;
+
+ // Set the current activeSurvey.
+ _devToolsUsage.activeSurvey = theSurveyName;
+ result = true;
+ }
+
+ return api.getCompleted(request, json.encode(result));
+ case apiGetSurveyActionTaken:
+ // Request setActiveSurvey has not been requested.
+ if (_devToolsUsage.activeSurvey == null) {
+ return api.badRequest('$errorNoActiveSurvey '
+ '- $apiGetSurveyActionTaken');
+ }
+ // SurveyActionTaken has the survey been acted upon (taken or dismissed)
+ return api.getCompleted(
+ request,
+ json.encode(_devToolsUsage.surveyActionTaken),
+ );
+ // TODO(terry): remove the query param logic for this request.
+ // setSurveyActionTaken should only be called with the value of true, so
+ // we can remove the extra complexity.
+ case apiSetSurveyActionTaken:
+ // Request setActiveSurvey has not been requested.
+ if (_devToolsUsage.activeSurvey == null) {
+ return api.badRequest('$errorNoActiveSurvey '
+ '- $apiSetSurveyActionTaken');
+ }
+ // Set the SurveyActionTaken.
+ // Has the survey been taken or dismissed..
+ final queryParams = request.requestedUri.queryParameters;
+ if (queryParams.containsKey(surveyActionTakenPropertyName)) {
+ _devToolsUsage.surveyActionTaken =
+ json.decode(queryParams[surveyActionTakenPropertyName]!);
+ }
+ return api.setCompleted(
+ request,
+ json.encode(_devToolsUsage.surveyActionTaken),
+ );
+ case apiGetSurveyShownCount:
+ // Request setActiveSurvey has not been requested.
+ if (_devToolsUsage.activeSurvey == null) {
+ return api.badRequest('$errorNoActiveSurvey '
+ '- $apiGetSurveyShownCount');
+ }
+ // SurveyShownCount how many times have we asked to take survey.
+ return api.getCompleted(
+ request,
+ json.encode(_devToolsUsage.surveyShownCount),
+ );
+ case apiIncrementSurveyShownCount:
+ // Request setActiveSurvey has not been requested.
+ if (_devToolsUsage.activeSurvey == null) {
+ return api.badRequest('$errorNoActiveSurvey '
+ '- $apiIncrementSurveyShownCount');
+ }
+ // Increment the SurveyShownCount, we've asked about the survey.
+ _devToolsUsage.incrementSurveyShownCount();
+ return api.getCompleted(
+ request,
+ json.encode(_devToolsUsage.surveyShownCount),
+ );
+ case apiGetBaseAppSizeFile:
+ final queryParams = request.requestedUri.queryParameters;
+ if (queryParams.containsKey(baseAppSizeFilePropertyName)) {
+ final filePath = queryParams[baseAppSizeFilePropertyName]!;
+ final fileJson = LocalFileSystem.devToolsFileAsJson(filePath);
+ if (fileJson == null) {
+ return api.badRequest('No JSON file available at $filePath.');
+ }
+ return api.getCompleted(request, fileJson);
+ }
+ return api.badRequest('Request for base app size file does not '
+ 'contain a query parameter with the expected key: '
+ '$baseAppSizeFilePropertyName');
+ case apiGetTestAppSizeFile:
+ final queryParams = request.requestedUri.queryParameters;
+ if (queryParams.containsKey(testAppSizeFilePropertyName)) {
+ final filePath = queryParams[testAppSizeFilePropertyName]!;
+ final fileJson = LocalFileSystem.devToolsFileAsJson(filePath);
+ if (fileJson == null) {
+ return api.badRequest('No JSON file available at $filePath.');
+ }
+ return api.getCompleted(request, fileJson);
+ }
+ return api.badRequest('Request for test app size file does not '
+ 'contain a query parameter with the expected key: '
+ '$testAppSizeFilePropertyName');
+ default:
+ return api.notImplemented(request);
+ }
+ }
+
+ // Accessing Flutter usage file e.g., ~/.flutter.
+ // NOTE: Only access the file if it exists otherwise Flutter Tool hasn't yet
+ // been run.
+ static final FlutterUsage? _usage =
+ FlutterUsage.doesStoreExist ? FlutterUsage() : null;
+
+ // Accessing DevTools usage file e.g., ~/.devtools
+ static final DevToolsUsage _devToolsUsage = DevToolsUsage();
+
+ static DevToolsUsage get devToolsPreferences => _devToolsUsage;
+
+ /// Logs a page view in the DevTools server.
+ ///
+ /// In the open-source version of DevTools, Google Analytics handles this
+ /// without any need to involve the server.
+ FutureOr<shelf.Response> logScreenView(shelf.Request request) =>
+ notImplemented(request);
+
+ /// Return the value of the property.
+ FutureOr<shelf.Response> getCompleted(shelf.Request request, String value) =>
+ shelf.Response.ok('$value');
+
+ /// Return the value of the property after the property value has been set.
+ FutureOr<shelf.Response> setCompleted(shelf.Request request, String value) =>
+ shelf.Response.ok('$value');
+
+ /// A [shelf.Response] for API calls that encountered a request problem e.g.,
+ /// setActiveSurvey not called.
+ ///
+ /// This is a 400 Bad Request response.
+ FutureOr<shelf.Response> badRequest([String? logError]) {
+ if (logError != null) print(logError);
+ return shelf.Response(HttpStatus.badRequest);
+ }
+
+ /// A [shelf.Response] for API calls that have not been implemented in this
+ /// server.
+ ///
+ /// This is a no-op 204 No Content response because returning 404 Not Found
+ /// creates unnecessary noise in the console.
+ FutureOr<shelf.Response> notImplemented(shelf.Request request) =>
+ shelf.Response(HttpStatus.noContent);
+}
diff --git a/pkg/dds/lib/src/devtools/usage.dart b/pkg/dds/lib/src/devtools/usage.dart
new file mode 100644
index 0000000..8e61b90
--- /dev/null
+++ b/pkg/dds/lib/src/devtools/usage.dart
@@ -0,0 +1,227 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// TODO(bkonyi): remove once package:devtools_server_api is available
+// See https://github.com/flutter/devtools/issues/2958.
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:path/path.dart' as path;
+import 'package:usage/usage_io.dart';
+
+import 'file_system.dart';
+
+/// Access the file '~/.flutter'.
+class FlutterUsage {
+ /// Create a new Usage instance; [versionOverride] and [configDirOverride] are
+ /// used for testing.
+ FlutterUsage({
+ String settingsName = 'flutter',
+ String? versionOverride,
+ String? configDirOverride,
+ }) {
+ _analytics = AnalyticsIO('', settingsName, '');
+ }
+
+ late Analytics _analytics;
+
+ /// Does the .flutter store exist?
+ static bool get doesStoreExist {
+ return LocalFileSystem.flutterStoreExists();
+ }
+
+ bool get isFirstRun => _analytics.firstRun;
+
+ bool get enabled => _analytics.enabled;
+
+ set enabled(bool value) => _analytics.enabled = value;
+
+ String get clientId => _analytics.clientId;
+}
+
+// Access the DevTools on disk store (~/.devtools/.devtools).
+class DevToolsUsage {
+ /// Create a new Usage instance; [versionOverride] and [configDirOverride] are
+ /// used for testing.
+ DevToolsUsage({
+ String? versionOverride,
+ String? configDirOverride,
+ }) {
+ LocalFileSystem.maybeMoveLegacyDevToolsStore();
+ properties = IOPersistentProperties(
+ storeName,
+ documentDirPath: LocalFileSystem.devToolsDir(),
+ );
+ }
+
+ static const storeName = '.devtools';
+
+ /// The activeSurvey is the property name of a top-level property
+ /// existing or created in the file ~/.devtools
+ /// If the property doesn't exist it is created with default survey values:
+ ///
+ /// properties[activeSurvey]['surveyActionTaken'] = false;
+ /// properties[activeSurvey]['surveyShownCount'] = 0;
+ ///
+ /// It is a requirement that the API apiSetActiveSurvey must be called before
+ /// calling any survey method on DevToolsUsage (addSurvey, rewriteActiveSurvey,
+ /// surveyShownCount, incrementSurveyShownCount, or surveyActionTaken).
+ String? _activeSurvey;
+
+ late IOPersistentProperties properties;
+
+ static const _surveyActionTaken = 'surveyActionTaken';
+ static const _surveyShownCount = 'surveyShownCount';
+
+ void reset() {
+ properties.remove('firstRun');
+ properties['enabled'] = false;
+ }
+
+ bool get isFirstRun {
+ properties['firstRun'] = properties['firstRun'] == null;
+ return properties['firstRun'];
+ }
+
+ bool get enabled {
+ if (properties['enabled'] == null) {
+ properties['enabled'] = false;
+ }
+
+ return properties['enabled'];
+ }
+
+ set enabled(bool? value) {
+ properties['enabled'] = value;
+ return properties['enabled'];
+ }
+
+ bool surveyNameExists(String? surveyName) => properties[surveyName] != null;
+
+ void _addSurvey(String? surveyName) {
+ assert(activeSurvey == surveyName);
+ rewriteActiveSurvey(false, 0);
+ }
+
+ String? get activeSurvey => _activeSurvey;
+
+ set activeSurvey(String? surveyName) {
+ _activeSurvey = surveyName;
+
+ if (!surveyNameExists(activeSurvey)) {
+ // Create the survey if property is non-existent in ~/.devtools
+ _addSurvey(activeSurvey);
+ }
+ }
+
+ /// Need to rewrite the entire survey structure for property to be persisted.
+ void rewriteActiveSurvey(bool? actionTaken, int? shownCount) {
+ properties[activeSurvey] = {
+ _surveyActionTaken: actionTaken,
+ _surveyShownCount: shownCount,
+ };
+ }
+
+ int? get surveyShownCount {
+ final prop = properties[activeSurvey];
+ if (prop[_surveyShownCount] == null) {
+ rewriteActiveSurvey(prop[_surveyActionTaken], 0);
+ }
+ return properties[activeSurvey][_surveyShownCount];
+ }
+
+ void incrementSurveyShownCount() {
+ surveyShownCount; // Ensure surveyShownCount has been initialized.
+ final prop = properties[activeSurvey];
+ rewriteActiveSurvey(prop[_surveyActionTaken], prop[_surveyShownCount] + 1);
+ }
+
+ bool get surveyActionTaken {
+ return properties[activeSurvey][_surveyActionTaken] == true;
+ }
+
+ set surveyActionTaken(bool? value) {
+ final prop = properties[activeSurvey];
+ rewriteActiveSurvey(value, prop[_surveyShownCount]);
+ }
+}
+
+abstract class PersistentProperties {
+ PersistentProperties(this.name);
+
+ final String name;
+
+ dynamic operator [](String key);
+
+ void operator []=(String key, dynamic value);
+
+ /// Re-read settings from the backing store.
+ ///
+ /// May be a no-op on some platforms.
+ void syncSettings();
+}
+
+const JsonEncoder _jsonEncoder = JsonEncoder.withIndent(' ');
+
+class IOPersistentProperties extends PersistentProperties {
+ IOPersistentProperties(
+ String name, {
+ String? documentDirPath,
+ }) : super(name) {
+ final String fileName = name.replaceAll(' ', '_');
+ documentDirPath ??= LocalFileSystem.devToolsDir();
+ _file = File(path.join(documentDirPath, fileName));
+ if (!_file.existsSync()) {
+ _file.createSync(recursive: true);
+ }
+ syncSettings();
+ }
+
+ IOPersistentProperties.fromFile(File file) : super(path.basename(file.path)) {
+ _file = file;
+ if (!_file.existsSync()) {
+ _file.createSync(recursive: true);
+ }
+ syncSettings();
+ }
+
+ late File _file;
+
+ Map? _map;
+
+ @override
+ dynamic operator [](String? key) => _map![key];
+
+ @override
+ void operator []=(String? key, dynamic value) {
+ if (value == null && !_map!.containsKey(key)) return;
+ if (_map![key] == value) return;
+
+ if (value == null) {
+ _map!.remove(key);
+ } else {
+ _map![key] = value;
+ }
+
+ try {
+ _file.writeAsStringSync(_jsonEncoder.convert(_map) + '\n');
+ } catch (_) {}
+ }
+
+ @override
+ void syncSettings() {
+ try {
+ String contents = _file.readAsStringSync();
+ if (contents.isEmpty) contents = '{}';
+ _map = jsonDecode(contents);
+ } catch (_) {
+ _map = {};
+ }
+ }
+
+ void remove(String propertyName) {
+ _map!.remove(propertyName);
+ }
+}
diff --git a/pkg/dds/pubspec.yaml b/pkg/dds/pubspec.yaml
index 62298b1..012f3e3 100644
--- a/pkg/dds/pubspec.yaml
+++ b/pkg/dds/pubspec.yaml
@@ -3,7 +3,7 @@
A library used to spawn the Dart Developer Service, used to communicate with
a Dart VM Service instance.
-version: 2.0.1
+version: 2.0.2
homepage: https://github.com/dart-lang/sdk/tree/master/pkg/dds
@@ -24,6 +24,7 @@
shelf_web_socket: ^1.0.0
sse: ^4.0.0
stream_channel: ^2.0.0
+ usage: ^4.0.0
vm_service: ^7.0.0
web_socket_channel: ^2.0.0
diff --git a/pkg/dds/test/common/fakes.dart b/pkg/dds/test/common/fakes.dart
index 7e11f9d..9ef5cf9 100644
--- a/pkg/dds/test/common/fakes.dart
+++ b/pkg/dds/test/common/fakes.dart
@@ -7,6 +7,7 @@
import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
import 'package:test/fake.dart';
import 'package:vm_service/vm_service.dart';
+import 'package:web_socket_channel/web_socket_channel.dart';
/// [FakePeer] implements the bare minimum of the [Peer] interface needed for
/// [DartDevelopmentService] to establish a connection with a VM service.
@@ -62,3 +63,18 @@
int _idCount = 0;
}
+
+class FakeWebSocketSink extends Fake implements WebSocketSink {
+ @override
+ Future close([int? closeCode, String? closeReason]) {
+ // Do nothing.
+ return Future.value();
+ }
+}
+
+/// [FakeWebSocketChannel] implements the bare minimum of the [WebSocket]
+/// interface required to finish DDS initialization.
+class FakeWebSocketChannel extends Fake implements WebSocketChannel {
+ @override
+ WebSocketSink get sink => FakeWebSocketSink();
+}
diff --git a/pkg/dds/test/handles_client_disconnect_state_error_test.dart b/pkg/dds/test/handles_client_disconnect_state_error_test.dart
index 816703b..c9d35fa 100644
--- a/pkg/dds/test/handles_client_disconnect_state_error_test.dart
+++ b/pkg/dds/test/handles_client_disconnect_state_error_test.dart
@@ -9,7 +9,6 @@
import 'package:dds/src/rpc_error_codes.dart';
import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
import 'package:pedantic/pedantic.dart';
-import 'package:test/fake.dart';
import 'package:test/test.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
@@ -42,19 +41,6 @@
}
}
-class FakeWebSocketSink extends Fake implements WebSocketSink {
- @override
- Future close([int? closeCode, String? closeReason]) {
- // Do nothing.
- return Future.value();
- }
-}
-
-class FakeWebSocketChannel extends Fake implements WebSocketChannel {
- @override
- WebSocketSink get sink => FakeWebSocketSink();
-}
-
void main() {
webSocketBuilder = (Uri _) => FakeWebSocketChannel();
peerBuilder =
diff --git a/pkg/dds/test/handles_shutdown_before_startup_test.dart b/pkg/dds/test/handles_shutdown_before_startup_test.dart
new file mode 100644
index 0000000..ae1b06a
--- /dev/null
+++ b/pkg/dds/test/handles_shutdown_before_startup_test.dart
@@ -0,0 +1,54 @@
+// Copyright (c) 2021, 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 'package:dds/dds.dart';
+import 'package:dds/src/dds_impl.dart';
+import 'package:test/test.dart';
+import 'package:web_socket_channel/web_socket_channel.dart';
+
+import 'common/fakes.dart';
+
+class ImmediatelyClosedPeer extends FakePeer {
+ // Immediately complete the returned future to act as if we've closed.
+ @override
+ Future<void> listen() => Future.value();
+}
+
+class ImmediatelyClosedErrorPeer extends FakePeer {
+ // Immediately complete the returned future with an error to act as if we've
+ // encountered a connection issue.
+ @override
+ Future<void> listen() => Future.error('Connection lost');
+}
+
+// Regression test for https://github.com/flutter/flutter/issues/86361.
+void main() {
+ webSocketBuilder = (Uri _) => FakeWebSocketChannel();
+
+ test('Shutdown before server startup complete', () async {
+ peerBuilder =
+ (WebSocketChannel _, dynamic __) async => ImmediatelyClosedPeer();
+ try {
+ await DartDevelopmentService.startDartDevelopmentService(
+ Uri(scheme: 'http'),
+ );
+ } on DartDevelopmentServiceException {
+ /* We expect to fail to start */
+ }
+ });
+
+ test('Error shutdown before server startup complete', () async {
+ peerBuilder =
+ (WebSocketChannel _, dynamic __) async => ImmediatelyClosedErrorPeer();
+ try {
+ await DartDevelopmentService.startDartDevelopmentService(
+ Uri(scheme: 'http'),
+ );
+ } on DartDevelopmentServiceException {
+ /* We expect to fail to start */
+ }
+ });
+}
diff --git a/runtime/vm/virtual_memory_win.cc b/runtime/vm/virtual_memory_win.cc
index c4f4269..1bab7a3 100644
--- a/runtime/vm/virtual_memory_win.cc
+++ b/runtime/vm/virtual_memory_win.cc
@@ -62,10 +62,7 @@
PAGE_READWRITE, nullptr);
if (address == nullptr) {
int error = GetLastError();
- const int kBufferSize = 1024;
- char error_buf[kBufferSize];
- FATAL2("Failed to reserve region for compressed heap: %d (%s)", error,
- Utils::StrError(error, error_buf, kBufferSize));
+ FATAL2("Failed to reserve region for compressed heap: %d", error);
}
VirtualMemoryCompressedHeap::Init(address);
}
diff --git a/tools/VERSION b/tools/VERSION
index 7d2ebaa..75a9854 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 14
PATCH 0
-PRERELEASE 318
+PRERELEASE 319
PRERELEASE_PATCH 0
\ No newline at end of file