Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ Future<AnalyticsController> get analyticsController async {

AnalyticsController? _analyticsController;

/// A synchronous check to see if analytics are enabled.
///
/// Returns `false` if analytics are disabled or not yet initialized.
bool get isAnalyticsEnabled =>
_analyticsController?.analyticsEnabled.value ?? false;

/// Whether the analytics controller has been initialized.
bool get isAnalyticsControllerInitialized => _analyticsController != null;

typedef AsyncAnalyticsCallback = FutureOr<void> Function();

class AnalyticsController {
Expand Down
16 changes: 15 additions & 1 deletion packages/devtools_app/lib/src/shared/server/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import 'package:http/http.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;

import '../analytics/analytics_controller.dart';
import '../development_helpers.dart';
import '../globals.dart';
import '../primitives/query_parameters.dart';
import '../primitives/storage.dart';
import '../primitives/utils.dart';

Expand Down Expand Up @@ -73,7 +75,19 @@ Uri buildDevToolsServerRequestUri(String url) {
// [_debugDevToolsServerFlag] environment variable declaration was not set
// using `--dart-define`.
const baseUri = _debugDevToolsServerEnvironmentVariable;
return Uri.parse(path.join(baseUri, url));
final uri = Uri.parse(path.join(baseUri, url));

final queryParams = DevToolsQueryParams.load();
// Forward the parent IDE name and the client-side analytics opt-out status
// to the server, so they can be propagated to any spawned subprocesses.
final newParams = <String, String>{
...uri.queryParameters,
if (queryParams.ide != null) 'ide': queryParams.ide!,
if (isAnalyticsControllerInitialized && !isAnalyticsEnabled)
'suppress_analytics': 'true',
};

return uri.replace(queryParameters: newParams);
}

/// Helper to catch any server request which could fail.
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools_app/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ dependencies:
sse: ^4.1.2
stack_trace: ^1.12.0
string_scanner: ^1.4.0
unified_analytics: ^7.0.0
unified_analytics: ^8.0.13
vm_service: ^15.0.2
vm_service_protos: ^2.0.0
vm_snapshot_analysis: ^0.7.6
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'dart:io';

import 'package:meta/meta.dart';
import 'package:path/path.dart' as path;
import 'package:unified_analytics/unified_analytics.dart';

class DeeplinkManager {
/// A regex to retrieve the json part from the stdout of Android analyzer.
Expand All @@ -32,6 +33,23 @@ class DeeplinkManager {
/// APIs.
static const kOutputJsonField = 'json';

/// Mappings from case-insensitive IDE query parameter values to their
/// corresponding [DashTool] enum values used by `package:unified_analytics`.
///
/// Contains multiple spelling and format variations (with/without hyphens
/// or suffixes) passed by different IDE integrations to ensure O(1) lookup.
static const _ideToDashToolMap = <String, DashTool>{
'vs-code': DashTool.vscodePlugins,
'vscode': DashTool.vscodePlugins,
'vscodeplugins': DashTool.vscodePlugins,
'intellij-idea': DashTool.intellijPlugins,
'intellij': DashTool.intellijPlugins,
'intellijplugins': DashTool.intellijPlugins,
'android-studio': DashTool.androidStudioPlugins,
'androidstudio': DashTool.androidStudioPlugins,
'androidstudioplugins': DashTool.androidStudioPlugins,
};

/// A regex to retrieve the file path from the stdout of iOS or Android
/// analyzers.
///
Expand All @@ -44,13 +62,39 @@ class DeeplinkManager {
Future<ProcessResult> runProcess(
String executable, {
required List<String> arguments,
String? ide,
bool suppressAnalytics = false,
}) {
final environment = <String, String>{
...getEnvironment(
currentTool: ide != null ? _mapIdeToDashTool(ide) : DashTool.devtools,
suppressAnalytics: suppressAnalytics,
),
if (ide != null) DashEnvVar.tool.name: ide,
};
Comment thread
pq marked this conversation as resolved.
Outdated

return Process.run(
executable,
arguments,
environment: environment,
);
}

DashTool _mapIdeToDashTool(String ide) {
final lowerIde = ide.toLowerCase();
final mappedTool = _ideToDashToolMap[lowerIde];
if (mappedTool != null) {
return mappedTool;
}

for (final value in DashTool.values) {
if (value.name.toLowerCase() == lowerIde) {
return value;
}
}
return DashTool.devtools;
}

@visibleForTesting
String getFlutterBinary() {
// FLUTTER_ROOT can be set by Dart-Code VSCode extension or dart shell
Expand Down Expand Up @@ -81,9 +125,16 @@ class DeeplinkManager {
Future<String> _runFlutterCommand(
List<String> arguments, {
required RegExp outputMatcher,
String? ide,
bool suppressAnalytics = false,
}) async {
final flutterPath = getFlutterBinary();
final result = await runProcess(flutterPath, arguments: arguments);
final result = await runProcess(
flutterPath,
arguments: arguments,
ide: ide,
suppressAnalytics: suppressAnalytics,
);
if (result.exitCode != 0) {
throw _FlutterProcessError(
'Flutter command exit with non-zero error code ${result.exitCode}\n${result.stderr}',
Expand Down Expand Up @@ -126,10 +177,14 @@ class DeeplinkManager {

Future<Map<String, Object?>> getAndroidBuildVariants({
required String rootPath,
String? ide,
bool suppressAnalytics = false,
}) {
return _runFlutterCommand(
<String>['analyze', '--android', '--list-build-variants', rootPath],
outputMatcher: _androidBuildVariantJsonRegex,
ide: ide,
suppressAnalytics: suppressAnalytics,
).then<Map<String, Object?>>(
_handleJsonOutput,
onError: _handleRunFlutterError,
Expand All @@ -139,6 +194,8 @@ class DeeplinkManager {
Future<Map<String, Object?>> getAndroidAppLinkSettings({
required String rootPath,
required String buildVariant,
String? ide,
bool suppressAnalytics = false,
}) {
return _runFlutterCommand(
<String>[
Expand All @@ -149,6 +206,8 @@ class DeeplinkManager {
rootPath,
],
outputMatcher: _outputFilePathRegex,
ide: ide,
suppressAnalytics: suppressAnalytics,
).then<Map<String, Object?>>(
_handleReadJsonFile,
onError: _handleRunFlutterError,
Expand All @@ -157,10 +216,14 @@ class DeeplinkManager {

Future<Map<String, Object?>> getIosBuildOptions({
required String rootPath,
String? ide,
bool suppressAnalytics = false,
}) {
return _runFlutterCommand(
<String>['analyze', '--ios', '--list-build-options', rootPath],
outputMatcher: _iosBuildOptionsJsonRegex,
ide: ide,
suppressAnalytics: suppressAnalytics,
).then<Map<String, Object?>>(
_handleJsonOutput,
onError: _handleRunFlutterError,
Expand All @@ -171,6 +234,8 @@ class DeeplinkManager {
required String rootPath,
required String configuration,
required String target,
String? ide,
bool suppressAnalytics = false,
}) {
return _runFlutterCommand(
<String>[
Expand All @@ -182,6 +247,8 @@ class DeeplinkManager {
rootPath,
],
outputMatcher: _outputFilePathRegex,
ide: ide,
suppressAnalytics: suppressAnalytics,
).then<Map<String, Object?>>(
_handleReadJsonFile,
onError: _handleRunFlutterError,
Expand Down
22 changes: 19 additions & 3 deletions packages/devtools_shared/lib/src/server/handlers/_deeplink.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ extension _DeeplinkApiHandler on Never {
if (missingRequiredParams != null) return missingRequiredParams;

final rootPath = queryParams[DeeplinkApi.deeplinkRootPathPropertyName]!;
final result =
await deeplinkManager.getAndroidBuildVariants(rootPath: rootPath);
final result = await deeplinkManager.getAndroidBuildVariants(
rootPath: rootPath,
ide: queryParams.ide,
suppressAnalytics: queryParams.suppressAnalytics,
);
return _resultOutputOrError(api, result);
}

Expand All @@ -47,6 +50,8 @@ extension _DeeplinkApiHandler on Never {
final result = await deeplinkManager.getAndroidAppLinkSettings(
rootPath: rootPath,
buildVariant: buildVariant,
ide: queryParams.ide,
suppressAnalytics: queryParams.suppressAnalytics,
);
return _resultOutputOrError(api, result);
}
Expand All @@ -65,7 +70,11 @@ extension _DeeplinkApiHandler on Never {
if (missingRequiredParams != null) return missingRequiredParams;

final rootPath = queryParams[DeeplinkApi.deeplinkRootPathPropertyName]!;
final result = await deeplinkManager.getIosBuildOptions(rootPath: rootPath);
final result = await deeplinkManager.getIosBuildOptions(
rootPath: rootPath,
ide: queryParams.ide,
suppressAnalytics: queryParams.suppressAnalytics,
);
return _resultOutputOrError(api, result);
}

Expand All @@ -90,6 +99,8 @@ extension _DeeplinkApiHandler on Never {
rootPath: queryParams[DeeplinkApi.deeplinkRootPathPropertyName]!,
configuration: queryParams[DeeplinkApi.xcodeConfigurationPropertyName]!,
target: queryParams[DeeplinkApi.xcodeTargetPropertyName]!,
ide: queryParams.ide,
suppressAnalytics: queryParams.suppressAnalytics,
);
return _resultOutputOrError(api, result);
}
Expand All @@ -107,3 +118,8 @@ extension _DeeplinkApiHandler on Never {
);
}
}

extension on Map<String, String> {
String? get ide => this['ide'];
bool get suppressAnalytics => this['suppress_analytics'] == 'true';
}
1 change: 1 addition & 0 deletions packages/devtools_shared/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dependencies:
path: ^1.8.0
shelf: ^1.1.0
sse: ^4.1.2
unified_analytics: ^8.0.13
vm_service: ">=13.0.0 <16.0.0"
web_socket_channel: '>=2.4.0 <4.0.0'
webkit_inspection_protocol: ">=0.5.0 <2.0.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,43 @@ Running Gradle task 'printBuildVariants'... 10.4s
);
});

test('getBuildVariants propagates parent IDE and analytics opt-out status',
() async {
const projectRoot = '/abc';
manager.expectedCommands.add(
TestCommand(
executable: manager.mockedFlutterBinary,
arguments: <String>[
'analyze',
'--android',
'--list-build-variants',
projectRoot,
],
ide: 'VS-Code',
suppressAnalytics: true,
result: ProcessResult(
0,
0,
r'''
Running Gradle task 'printBuildVariants'... 10.4s
["debug"]
''',
'',
),
),
);
final response = await manager.getAndroidBuildVariants(
rootPath: projectRoot,
ide: 'VS-Code',
suppressAnalytics: true,
);
expect(response[DeeplinkManager.kErrorField], isNull);
expect(
response[DeeplinkManager.kOutputJsonField],
'["debug"]',
);
});

test(
'getBuildVariants return internal server error if command failed',
() async {
Expand Down Expand Up @@ -217,15 +254,19 @@ class StubbedDeeplinkManager extends DeeplinkManager {
Future<ProcessResult> runProcess(
String executable, {
required List<String> arguments,
String? ide,
bool suppressAnalytics = false,
}) async {
if (expectedCommands.isNotEmpty) {
final expectedCommand = expectedCommands.removeAt(0);
expect(expectedCommand.executable, executable);
expect(executable, expectedCommand.executable);
expect(
const ListEquality<String>()
.equals(expectedCommand.arguments, arguments),
.equals(arguments, expectedCommand.arguments),
isTrue,
);
expect(ide, expectedCommand.ide);
expect(suppressAnalytics, expectedCommand.suppressAnalytics);
return expectedCommand.result;
}
throw 'Received unexpected command: $executable ${arguments.join(' ')}';
Expand All @@ -236,10 +277,14 @@ class TestCommand {
const TestCommand({
required this.executable,
required this.arguments,
this.ide,
this.suppressAnalytics = false,
required this.result,
});
final String executable;
final List<String> arguments;
final String? ide;
final bool suppressAnalytics;
final ProcessResult result;

@override
Expand Down
Loading
Loading